Storybook Component Development
Storybook Component Development Writing Stories // Button.stories.tsx import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button';…
Storybook Component Development
Writing Stories
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
// Meta — component-level config
const meta: Meta<typeof Button> = {
title: 'UI/Button', // sidebar grouping: UI > Button
component: Button,
tags: ['autodocs'], // auto-generate docs page
parameters: {
layout: 'centered', // center in canvas
},
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
description: 'Visual style of the button',
},
size: {
control: { type: 'radio' },
options: ['sm', 'md', 'lg'],
},
onClick: { action: 'clicked' }, // log action in Actions panel
disabled: { control: 'boolean' },
},
};
export default meta;
type Story = StoryObj<typeof Button>;
// Stories — individual states/variants
export const Primary: Story = {
args: {
children: 'Click me',
variant: 'primary',
size: 'md',
},
};
export const Disabled: Story = {
args: { ...Primary.args, disabled: true },
};
export const Loading: Story = {
args: { ...Primary.args, isLoading: true },
parameters: {
docs: { description: { story: 'Shows spinner while loading' } },
},
};
// Composing stories
export const AllVariants: Story = {
render: () => (
<div style={{ display: 'flex', gap: '8px' }}>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="danger">Danger</Button>
</div>
),
};Decorators, Mocking & Testing
// Global decorators — .storybook/preview.tsx
import type { Preview } from '@storybook/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const preview: Preview = {
decorators: [
(Story) => (
<QueryClientProvider client={new QueryClient()}>
<div style={{ padding: '2rem' }}>
<Story />
</div>
</QueryClientProvider>
),
],
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: { matchers: { color: /(background|color)$/i } },
},
};
export default preview;
// Mock API calls — msw-storybook-addon
import { http, HttpResponse } from 'msw';
export const WithUser: Story = {
parameters: {
msw: {
handlers: [
http.get('/api/users/:id', ({ params }) =>
HttpResponse.json({ id: params.id, name: 'Alice', role: 'admin' })
),
],
},
},
};
// Interaction tests — @storybook/test
import { expect, userEvent, within } from '@storybook/test';
export const SubmitForm: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.type(canvas.getByLabelText('Email'), 'alice@example.com');
await userEvent.click(canvas.getByRole('button', { name: 'Submit' }));
await expect(canvas.getByText('Success!')).toBeInTheDocument();
},
};
// Commands
// npx storybook dev -p 6006 — start dev server
// npx storybook build — build static site
// npx storybook test — run interaction tests