Storybook - Story Writing
Write well-structured, maintainable Storybook stories using Component Story Format 3 (CSF3) that showcase component variations and ensure consistent rendering.
Key Concepts Component Story Format 3 (CSF3)
CSF3 is the modern Storybook format that uses object syntax for stories:
import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button';
const meta = {
title: 'Components/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
backgroundColor: { control: 'color' },
},
} satisfies Meta
export default meta;
type Story = StoryObj
export const Primary: Story = { args: { primary: true, label: 'Button', }, };
export const Secondary: Story = { args: { label: 'Button', }, };
Story Organization One story file per component: Component.stories.tsx Use descriptive story names: Primary, Secondary, Large, Disabled Group related stories under a title hierarchy: Components/Forms/Input Default Export (Meta)
The default export defines metadata for all stories:
const meta = {
title: 'Components/Button', // Navigation path
component: Button, // Component reference
parameters: {}, // Story-level config
tags: ['autodocs'], // Enable auto-documentation
argTypes: {}, // Control types
decorators: [], // Wrappers for stories
} satisfies Meta
Best Practices 1. Use TypeScript for Type Safety import type { Meta, StoryObj } from '@storybook/react';
const meta = {
component: Button,
} satisfies Meta
type Story = StoryObj
- Show All Component States
Create stories for each meaningful state:
export const Default: Story = { args: { label: 'Click me', }, };
export const Loading: Story = { args: { label: 'Loading...', loading: true, }, };
export const Disabled: Story = { args: { label: 'Disabled', disabled: true, }, };
export const WithIcon: Story = { args: { label: 'Download', icon: 'download', }, };
- Use Sensible Defaults export const Primary: Story = { args: { primary: true, label: 'Button', size: 'medium', }, };
// Extend existing stories export const PrimaryLarge: Story = { ...Primary, args: { ...Primary.args, size: 'large', }, };
-
Add Descriptive Parameters export const WithTooltip: Story = { args: { label: 'Hover me', tooltip: 'Click to submit', }, parameters: { docs: { description: { story: 'Shows a tooltip on hover to provide additional context.', }, }, }, };
-
Use Decorators for Context import { RouterDecorator } from '../decorators';
const meta = { component: Navigation, decorators: [ (Story) => (
Common Patterns Form Components export const EmptyForm: Story = { args: { onSubmit: (data) => console.log(data), }, };
export const PrefilledForm: Story = { args: { defaultValues: { email: 'user@example.com', name: 'John Doe', }, }, };
export const WithValidationErrors: Story = { args: { errors: { email: 'Invalid email format', name: 'Name is required', }, }, };
Layout Components
export const WithSidebar: Story = {
args: {
sidebar:
Data-Driven Components const mockData = [ { id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }, { id: 3, name: 'Item 3' }, ];
export const WithData: Story = { args: { items: mockData, }, };
export const Empty: Story = { args: { items: [], emptyMessage: 'No items found', }, };
Responsive Components export const Mobile: Story = { args: { variant: 'mobile', }, parameters: { viewport: { defaultViewport: 'mobile1', }, }, };
export const Desktop: Story = { args: { variant: 'desktop', }, parameters: { viewport: { defaultViewport: 'desktop', }, }, };
Anti-Patterns ❌ Don't Use Template Binding (CSF2) // Bad - Old CSF2 format const Template = (args) => ; export const Primary = Template.bind({}); Primary.args = { label: 'Button' };
// Good - CSF3 format export const Primary: Story = { args: { label: 'Button' }, };
❌ Don't Mix Logic in Stories
// Bad
export const Complex: Story = {
render: (args) => {
const [state, setState] = useState(false);
useEffect(() => {
// Complex side effects
}, []);
return
// Good - Move logic to component or use play functions export const Complex: Story = { args: { initialState: false }, };
❌ Don't Hardcode Repetitive Props // Bad export const Story1: Story = { args: { label: 'Button', size: 'medium', theme: 'light' }, }; export const Story2: Story = { args: { label: 'Submit', size: 'medium', theme: 'light' }, };
// Good - Use meta-level defaults
const meta = {
component: Button,
args: {
size: 'medium',
theme: 'light',
},
} satisfies Meta
export const Story1: Story = { args: { label: 'Button' }, }; export const Story2: Story = { args: { label: 'Submit' }, };
❌ Don't Skip Story Types // Bad - Missing type annotation export const Primary = { args: { label: 'Button' }, };
// Good - With type export const Primary: Story = { args: { label: 'Button' }, };
Related Skills storybook-args-controls: Advanced arg configuration and interactive controls storybook-play-functions: Automated interaction testing within stories storybook-component-documentation: Auto-generating component documentation