Storybook Component Library
Interactive component development and documentation with Storybook
Overview
Portfolio OS uses Storybook as its component development environment and living documentation system. Storybook allows you to develop, test, and document UI components in isolation.
Storybook provides:
- Interactive Development - Build and test components without running the full app
- Visual Testing - See all component variants and states in one place
- Documentation - Auto-generated docs with prop tables and usage examples
- Accessibility Testing - Built-in a11y checks and keyboard navigation testing
- Responsive Testing - View components across different viewport sizes
Note:
Storybook runs independently from your Next.js application on port 6006.
Quick Start
Starting Storybook
# Start Storybook development server
pnpm --filter @mindware-blog/site storybook
# Or from the workspace root
pnpm storybook --filter @mindware-blog/site
Storybook will start at http://localhost:6006
Building for Production
# Build static Storybook
pnpm --filter @mindware-blog/site build-storybook
# Serve the built Storybook
pnpm --filter @mindware-blog/site storybook:serve
Component Categories
UI Components
Core design system components located in components/ui/:
- Button - Various button styles and sizes
- Card - Container component with header, content, and footer
- Badge - Labels and tags
- Input - Text inputs with validation states
- Select - Dropdown selection components
- Dialog - Modal dialogs and overlays
- Tooltip - Contextual help tooltips
Project Components
Components for portfolio and project showcases in components/projects/:
- Gallery - Image gallery with lightbox
- FeatureGrid - Responsive feature showcase grid
- InlineCaseStudy - Case study highlight blocks
Blog Components
Blog-specific components in components/features/blog/:
- PostCard - Blog post preview cards
- PostHeader - Article headers with metadata
- ModernPostCard - Enhanced post card design
- TableOfContents - Dynamic TOC for articles
Writing Stories
Basic Story Structure
Stories are written in TypeScript with the .stories.tsx extension:
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './button';
const meta = {
title: 'UI/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: 'Click me',
variant: 'default',
},
};
Story Naming Conventions
- Title: Use hierarchical paths like
UI/ButtonorProjects/Gallery - Story Names: Descriptive names like
Default,WithIcon,Disabled - File Names: Match component names:
button.stories.tsx
Creating Comprehensive Stories
Include these essential variants:
// Default state
export const Default: Story = {
args: { children: 'Button' },
};
// Disabled state
export const Disabled: Story = {
args: {
children: 'Disabled',
disabled: true,
},
};
// Loading state
export const Loading: Story = {
args: {
children: 'Loading...',
disabled: true,
},
};
Configuration
Main Configuration
Storybook configuration is in .storybook/main.ts:
import type { StorybookConfig } from '@storybook/nextjs';
const config: StorybookConfig = {
stories: [
'../components/**/*.stories.@(js|jsx|ts|tsx|mdx)',
'../app/**/*.stories.@(js|jsx|ts|tsx|mdx)',
'../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)',
],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/nextjs',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;
Preview Configuration
Global decorators and parameters in .storybook/preview.ts:
import type { Preview } from '@storybook/react';
import '../app/globals.css';
const preview: Preview = {
parameters: {
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#0a0a0a' },
],
},
},
};
export default preview;
Features
Autodocs
Add the autodocs tag to automatically generate documentation:
const meta = {
title: 'UI/Button',
component: Button,
tags: ['autodocs'], // Enables automatic documentation
} satisfies Meta<typeof Button>;
Controls
Interactive controls for props:
argTypes: {
variant: {
control: 'select',
options: ['default', 'secondary', 'outline'],
description: 'Visual style of the button',
},
size: {
control: 'select',
options: ['sm', 'default', 'lg'],
},
disabled: {
control: 'boolean',
},
}
Actions
Log component interactions:
export const WithAction: Story = {
args: {
onClick: () => console.log('Button clicked!'),
},
};
Viewport Testing
Test responsive behavior:
parameters: {
viewport: {
defaultViewport: 'mobile1',
},
}
Best Practices
DO ✅
- Write stories for all public components
- Include all variants and states
- Add descriptive documentation
- Test accessibility
- Use TypeScript for type safety
- Keep stories focused and simple
DON'T ❌
- Don't include business logic in stories
- Don't rely on external API calls
- Don't test implementation details
- Don't forget edge cases
- Don't skip accessibility checks
Integration with Testing
Visual Regression Testing
Storybook stories can be used for visual regression testing:
# Take snapshots of all stories
pnpm test:visual:storybook
Accessibility Testing
Built-in a11y addon checks for common issues:
- Navigate to the Accessibility tab in Storybook
- View violations and passes
- Fix any issues before committing
Interaction Testing
Test user interactions with @storybook/test:
import { expect, userEvent, within } from '@storybook/test';
export const ClickTest: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button');
await userEvent.click(button);
await expect(button).toHaveTextContent('Clicked');
},
};
CI/CD Integration
GitHub Actions Workflow
Storybook can be built and deployed in CI:
name: Storybook
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
build-storybook:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Storybook
run: pnpm --filter @mindware-blog/site build-storybook
- name: Upload Storybook
uses: actions/upload-artifact@v4
with:
name: storybook
path: apps/site/storybook-static
Deployment
Deploy Storybook to static hosting:
# Build Storybook
pnpm --filter @mindware-blog/site build-storybook
# Deploy to Vercel, Netlify, or GitHub Pages
vercel deploy apps/site/storybook-static
Known Limitations
Note:
Current Status: Storybook infrastructure is complete, but there is a known compatibility issue between Storybook 8.6.14 and Next.js 15.5.2 that prevents the dev server and build from running successfully.
Next.js 15 Compatibility Issue
Issue: Webpack module resolution errors when starting Storybook
Affected Versions:
- Next.js 15.5.2 (App Router)
- Storybook 8.6.14
- @storybook/nextjs 8.6.14
Status: Known ecosystem compatibility issue tracked in Storybook Issue #30944
What Works:
- ✅ All component stories are written and ready (40+ stories)
- ✅ Documentation is complete
- ✅ CI/CD integration configured
- ✅ Story files serve as component documentation
- ✅ Configuration files are correct
Workarounds:
-
View Stories as Code Examples
- All
.stories.tsxfiles incomponents/can be read directly - Each story demonstrates component usage with props
- Full TypeScript types included
- All
-
Future Resolution Options:
- Storybook 9 (Beta) - Has improved Next.js 15 support
- Next.js 14 LTS - Known to work with Storybook 8.x
- Ecosystem Fix - May be resolved in future patch releases
Timeline: Expected resolution Q1 2025 when Storybook 9 reaches stable release
Impact: Development only - does not affect production Next.js application
Troubleshooting
Common Issues
Port 6006 already in use
Kill the process using port 6006:
# Windows
netstat -ano | findstr :6006
taskkill /PID <PID> /F
# Mac/Linux
lsof -ti:6006 | xargs kill -9
# Or use a different port
pnpm storybook -- -p 6007
Styles not loading correctly
Ensure global styles are imported in .storybook/preview.ts:
import '../app/globals.css';
For Tailwind, make sure the config is properly set up in Storybook's webpack config.
Next.js Image component not working
The @storybook/nextjs framework handles Next.js Image components automatically. If issues persist:
- Ensure you're using
@storybook/nextjs(not@storybook/react) - Check that
staticDirsis configured in.storybook/main.ts - Use relative paths for local images
Examples
Complete Button Story
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './button';
import { Plus, Download } from 'lucide-react';
const meta = {
title: 'UI/Button',
component: Button,
parameters: {
layout: 'centered',
docs: {
description: {
component: 'A versatile button component with multiple variants.',
},
},
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'],
},
size: {
control: 'select',
options: ['default', 'sm', 'lg', 'icon'],
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: 'Button',
},
};
export const WithIcon: Story = {
args: {
children: (
<>
<Plus className="mr-2 h-4 w-4" />
Add Item
</>
),
},
};
export const AllVariants: Story = {
render: () => (
<div className="flex gap-2">
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button variant="destructive">Destructive</Button>
</div>
),
};
Resources
Documentation
Addons
Internal Resources
Next Steps
Create Your First Story
Follow the guide to create stories for your components
Configure Addons
Enhance Storybook with additional functionality
Deploy Storybook
Share your component library with your team
Visual Testing
Set up automated visual regression testing
Need Help? Check the troubleshooting section or create an issue in the repository.