From milky-way-agent
Storybook story writing using Component Story Format (CSF) 3.0 with best practices. Use in the following situations: (1) When writing new story files (.stories.tsx), (2) When modifying existing stories, (3) When configuring Storybook Args, Decorators, Parameters, (4) When the task includes keywords 'story', 'stories', 'storybook'.
How this skill is triggered — by the user, by Claude, or both
Slash command
/milky-way-agent:generating-storiesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
[comment]: # 'Based on the following skill definition: https://github.com/DaleStudy/skills/blob/main/skills/storybook/SKILL.md'
Use the latest Component Story Format 3.0. It's more concise and type-safe.
// ❌ CSF 2.0 (Don't)
export default {
title: 'Components/Button',
component: Button
};
export const Primary = () => <Button variant="primary">Click me</Button>;
// ✅ CSF 3.0 (Do)
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
component: Button,
args: {
variant: 'primary',
children: 'Click me'
}
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {};
export const Secondary: Story = {
args: {
variant: 'secondary'
}
};
Define component Props as Args to enable interactive manipulation in the Controls panel.
argTypes.defaultValue). Placing default values in Meta's args causes them to be automatically selected in the Controls panel// ❌ Hardcoded Props
export const Disabled: Story = {
render: () => <Button disabled>Disabled</Button>
};
// ❌ Duplicating the same args across multiple stories
export const Primary: Story = {
args: { children: 'Click me', variant: 'primary' }
};
export const Secondary: Story = {
args: { children: 'Click me', variant: 'secondary' }
};
// ✅ Declare common args in Meta, override only the differences in each story
const meta: Meta<typeof Button> = {
component: Button,
args: {
children: 'Click me',
variant: 'primary'
}
};
export const Primary: Story = {};
export const Secondary: Story = {
args: { variant: 'secondary' }
};
export const Disabled: Story = {
args: { disabled: true }
};
// ❌ Explicitly specifying the title (Don't, exception: using Figma component name)
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button
};
// ✅ Omit title — auto-inferred from file path (Do, for most components)
const meta: Meta<typeof Button> = {
component: Button
};
Leverage both type checking and inference by manually specifying the type.
// ❌ Type-inference not possible (Don't)
const meta = {
component: Button
};
// ✅ Both type checking and inference possible (Do)
const meta: Meta<typeof Button> = {
component: Button
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
color: 'dark'
}
};
export const Yellow: Story = {
args: {
// ✅ Re-use args from the Primary story for
// readability and conciseness.
...Primary.args,
color: 'yellow'
}
};
Apply common wrappers or Providers as Decorators.
// Apply Decorator to individual stories
export const WithTheme: Story = {
decorators: [
(Story) => (
<ThemeProvider theme="dark">
<Story />
</ThemeProvider>
)
]
};
// Apply Decorator to all stories
const meta: Meta<typeof Button> = {
component: Button,
decorators: [
(Story) => (
<div
style={{
height: '100vh',
width: '100vw',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Story />
</div>
)
]
};
const meta: Meta<typeof Button> = {
component: Button,
parameters: {
layout: 'centered', // Center-align the story
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#000000' }
]
}
}
};
// Override parameters in an individual story
export const OnDark: Story = {
parameters: {
backgrounds: { default: 'dark' }
}
};
Storybook automatically applies the optimal argType from the component function's TypeScript types. Manually overriding requires keeping argType in sync every time the component type changes, so do not specify argTypes directly without a valid reason.
Cases when manual specification is justified:
ReactNode, but text input is needed in Controls → control: 'text'argTypescontrol: false// ❌ Unnecessary manual argType specification — breaks when type changes (Don't)
const meta: Meta<typeof Button> = {
component: Button,
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'tertiary']
},
disabled: {
control: 'boolean'
}
}
};
// ✅ Leave it to type inference, manually specify only when necessary (Do)
const meta: Meta<typeof Button> = {
component: Button,
argTypes: {
// When the type is ReactNode but text input is needed
children: { control: 'text' }
}
};
// ✅ When fixing a prop in a specific story — control: false (Do)
export const Horizontal: Story = {
args: { orientation: 'horizontal' },
argTypes: {
orientation: { control: false } // Always horizontal in this story
}
};
import { Meta, StoryObj } from '@storybook/react-webpack5';
import { Button } from './Button';
// 1. Meta definition — omit title, declare common args, let TypeScript infer argTypes
const meta: Meta<typeof Button> = {
component: Button
};
export default meta;
type Story = StoryObj<typeof Button>;
// 2. Default story — use Meta args as-is
export const Primary: Story = {};
// 3. Variant stories — override only the differences
export const Yellow: Story = {
args: {
...Primary.args,
color: 'yellow'
}
};
// 4. Stories requiring a fixed prop — use control: false
export const Horizontal: Story = {
args: { orientation: 'horizontal' },
argTypes: {
orientation: { control: false }
}
};
// 5. When complex state or context is required
export const WithCustomTheme: Story = {
decorators: [
(Story) => (
<ThemeProvider theme="custom">
<Story />
</ThemeProvider>
)
]
};
Principle: Most argTypes are auto-inferred by Storybook from the component type. The following is only used when auto-inference is inappropriate. Declare default values in
args, not inargTypes.defaultValue.
argTypes: {
// When the type is ReactNode but text input is needed
children: { control: 'text' },
// Range slider (when auto-inference is not appropriate)
opacity: {
control: { type: 'range', min: 0, max: 1, step: 0.1 },
},
// Action event handler
onClick: { action: 'clicked' },
// Disable control (fix prop in a specific story)
orientation: { control: false },
}
// 1. Style wrapper
(Story) => (
<div style={{ padding: '12px' }}>
<Story />
</div>
)
// 2. Theme Provider
(Story) => (
<ThemeProvider theme="dark">
<Story />
</ThemeProvider>
)
// 3. Ditto (translation) Provider
(Story: any) => (
<DittoProvider source={ditto} variant={DITTO_LOCALE.de}>
<Story />
</DittoProvider>
)
import React from 'react').decoratorsOneScreen decorator in the meta definition:const meta: Meta<typeof Component> = {
component: Component,
decorators: decoratorsOneScreen
};
Component.tsx # Component implementation
Component.module.css # CSS style definitions of the Component
Component.stories.tsx # Story file (same directory)
Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub vialytics/claude-skills --plugin milky-way-agent