storybook
SKILL.md
Storybook 8 Best Practices
Setup
npx storybook@latest init
This auto-detects your framework (React, Vue, etc.) and configures everything.
Writing Stories
Basic Story (CSF3)
// button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./button";
const meta = {
title: "Components/Button",
component: Button,
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["primary", "secondary", "danger"],
},
size: {
control: "radio",
options: ["sm", "md", "lg"],
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
variant: "primary",
children: "Click me",
},
};
export const Secondary: Story = {
args: {
variant: "secondary",
children: "Cancel",
},
};
export const Disabled: Story = {
args: {
...Primary.args,
disabled: true,
},
};
Key Concepts
- Meta — default configuration for all stories in the file (component, title, argTypes).
- Story — a specific state of the component with defined
args. - Args — props passed to the component, editable via Controls panel.
- Tags —
["autodocs"]auto-generates a documentation page.
Args and Controls
Controls are auto-generated from TypeScript props. Customize them with argTypes:
argTypes: {
onClick: { action: "clicked" }, // log to Actions panel
color: { control: "color" }, // color picker
count: { control: { type: "range", min: 0, max: 100 } },
status: {
control: "select",
options: ["idle", "loading", "error"],
description: "Current status of the widget",
table: { defaultValue: { summary: "idle" } },
},
},
Decorators
Wrap stories with providers, layouts, or context:
// Per-story
export const WithTheme: Story = {
decorators: [
(Story) => (
<ThemeProvider theme="dark">
<Story />
</ThemeProvider>
),
],
};
// Global (in .storybook/preview.tsx)
const preview: Preview = {
decorators: [
(Story) => (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
),
],
};
Interaction Testing
Write tests inside stories using the play function:
import { expect, fn, userEvent, within } from "@storybook/test";
export const SubmitForm: Story = {
args: {
onSubmit: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
await userEvent.type(canvas.getByLabelText("Email"), "test@example.com");
await userEvent.type(canvas.getByLabelText("Password"), "password123");
await userEvent.click(canvas.getByRole("button", { name: "Sign in" }));
await expect(args.onSubmit).toHaveBeenCalledWith({
email: "test@example.com",
password: "password123",
});
},
};
export const ValidationError: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole("button", { name: "Sign in" }));
await expect(canvas.getByText("Email is required")).toBeInTheDocument();
},
};
- Import testing utilities from
@storybook/test(not directly from Testing Library). - Use
fn()for spy/mock functions with Actions panel integration. - Use
within(canvasElement)to scope queries to the story's render area. - Tests run in the browser and are visible in the Interactions panel.
Running Tests
# Run interaction tests via test-runner
npx test-storybook
# With coverage
npx test-storybook --coverage
Component Documentation
Autodocs
Add tags: ["autodocs"] to meta for auto-generated docs:
const meta = {
title: "Components/Button",
component: Button,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: "Primary UI button for user actions.",
},
},
},
} satisfies Meta<typeof Button>;
MDX Docs
For custom documentation pages:
{/* button.mdx */}
import { Canvas, Meta, Story } from "@storybook/blocks";
import \* as ButtonStories from "./button.stories";
<Meta of={ButtonStories} />
# Button
Buttons trigger actions. Use primary for the main CTA and secondary for alternatives.
<Canvas of={ButtonStories.Primary} />
## Variants
<Canvas of={ButtonStories.Secondary} />
<Canvas of={ButtonStories.Disabled} />
Story Organization
File Structure
Colocate stories with components:
components/
└── button/
├── button.tsx
├── button.stories.tsx
├── button.test.tsx
└── index.ts
Naming Hierarchy
// title creates the sidebar structure
title: "Components/Forms/Input"; // Components > Forms > Input
title: "Components/Button"; // Components > Button
title: "Pages/Dashboard"; // Pages > Dashboard
Parameters
Configure story-level or global behavior:
export const Mobile: Story = {
parameters: {
viewport: { defaultViewport: "mobile1" },
layout: "fullscreen",
},
};
export const DarkMode: Story = {
parameters: {
backgrounds: { default: "dark" },
},
};
Loaders
Fetch data before a story renders:
export const WithData: Story = {
loaders: [
async () => ({
users: await fetch("/api/users").then((r) => r.json()),
}),
],
render: (args, { loaded: { users } }) => <UserList users={users} {...args} />,
};
Configuration
Main Config
// .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(ts|tsx|mdx)"],
addons: ["@storybook/addon-essentials", "@storybook/addon-interactions", "@storybook/addon-a11y"],
framework: "@storybook/react-vite",
typescript: {
reactDocgen: "react-docgen-typescript",
},
};
export default config;
Useful Addons
| Addon | Purpose |
|---|---|
@storybook/addon-essentials |
Controls, Actions, Viewport, Backgrounds, Docs |
@storybook/addon-interactions |
Step-through interaction testing |
@storybook/addon-a11y |
Accessibility audit via axe-core |
@storybook/addon-designs |
Embed Figma designs alongside stories |
@chromatic-com/storybook |
Visual regression testing with Chromatic |
Patterns
- One story per meaningful state — Primary, Disabled, Loading, Error, Empty, WithData.
- Use args composition — spread a base story's args into variants instead of duplicating.
- Test edge cases — long text, empty data, error states, loading states, RTL.
- Use decorators for context — providers, layouts, themes.
- Keep stories independent — each story should work in isolation.
- Add
autodocsto all public components for automatic documentation.
Weekly Installs
2
Repository
grahamcrackers/skillsFirst Seen
Feb 28, 2026
Security Audits
Installed on
cline2
gemini-cli2
github-copilot2
codex2
kimi-cli2
cursor2