📖 Storybook

On this page

Stories (CSF3) Jump to heading

This is a good starter that includes most things you’ll need when creating stories.

// MyComponent.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { MyComponent } from './MyComponent'

const meta = {
title: 'Components/MyComponent',
component: MyComponent,
// Autodocs
tags: ['autodocs'],
// Default args for all stories
args: {
prop1: 'Default value',
},
} satisfies Meta<typeof MyComponent>

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
prop1: 'Something',
prop2: true,
},
}

export const OnDark: Story = {
...Default,
parameters: {
backgrounds: { default: 'dark' },
},
}

Custom render function Jump to heading

export const WithWrapper: Story = {
render: (args) => (
<div style={{ maxWidth: '300px' }}>
<MyComponent {...args} />
</div>
),
}

Play function (interaction testing) Jump to heading

Play functions let you simulate user interactions for testing.

// LoginForm.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { within, userEvent, expect } from '@storybook/test'
import { LoginForm } from './LoginForm'

const meta = {
title: 'Forms/LoginForm',
component: LoginForm,
} satisfies Meta<typeof LoginForm>

export default meta
type Story = StoryObj<typeof meta>

export const FilledForm: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)

await userEvent.type(
canvas.getByLabelText('Email'),
'email@provider.com'
)
await userEvent.type(
canvas.getByLabelText('Password'),
'a-random-password'
)
await userEvent.click(canvas.getByRole('button', { name: 'Sign in' }))

await expect(canvas.getByText('Welcome back!')).toBeInTheDocument()
},
}

Args and argTypes Jump to heading

const meta = {
title: 'Components/Button',
component: Button,
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'ghost'],
},
size: {
control: 'radio',
options: ['sm', 'md', 'lg'],
},
disabled: { control: 'boolean' },
onClick: { action: 'clicked' },
},
} satisfies Meta<typeof Button>

Decorators Jump to heading

const meta = {
title: 'Components/Card',
component: Card,
decorators: [
(Story) => (
<div style={{ padding: '2rem' }}>
<Story />
</div>
),
],
} satisfies Meta<typeof Card>

Non-story exports Jump to heading

import type { Meta, StoryObj } from '@storybook/react'
import { MyComponent } from './MyComponent'

const meta = {
title: 'MyComponent',
component: MyComponent,
// Only include these as stories
includeStories: ['Default', 'WithData'],
// Or exclude anything matching pattern
excludeStories: /.*Data$/,
} satisfies Meta<typeof MyComponent>

export default meta
type Story = StoryObj<typeof meta>

// This won't be a story (excluded by pattern)
export const mockData = { foo: 1, bar: 'baz' }

export const Default: Story = {}

export const WithData: Story = {
args: { data: mockData },
}

Config Jump to heading

main.ts Jump to heading

// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite'

const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
}

export default config

preview.ts Jump to heading

// .storybook/preview.ts
import type { Preview } from '@storybook/react'
import '../src/styles/globals.css'

const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#1a1a1a' },
],
},
},
decorators: [
(Story) => (
<div style={{ fontFamily: 'system-ui, sans-serif' }}>
<Story />
</div>
),
],
}

export default preview

Useful addons Jump to heading

Testing with Storybook Jump to heading

Stories can be reused in unit tests using composeStories:

// MyComponent.test.tsx
import { composeStories } from '@storybook/react'
import { render, screen } from '@testing-library/react'
import * as stories from './MyComponent.stories'

const { Default, WithData } = composeStories(stories)

test('renders default state', () => {
render(<Default />)
expect(screen.getByText('Hello')).toBeInTheDocument()
})

test('renders with data', () => {
render(<WithData />)
expect(screen.getByText('Data loaded')).toBeInTheDocument()
})

← Back home