Skip to main content
skill CX Codex

storybook-story-writing — Claude Code skill: CSF3 stories that build

Writes and edits Storybook stories for your components: CSF3 format, every meaningful state, a build that passes. Useful when stories drift and break the build.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add TheBushidoCollective/han --skill storybook-story-writing -a claude-code
Open on GitHub

What storybook-story-writing does

The skill kicks in when you create or modify *.stories.tsx files. It keeps stories in CSF3: a meta object with satisfies Meta<typeof Component>, typed StoryObj, one export per state. It also watches that the Storybook build goes through.

Inside are rules and examples. The meta export carries title, component, parameters, tags: ['autodocs'] and argTypes. Shared props like size and theme belong in meta-level args instead of being repeated in every story. Decorators handle context, parameters.viewport handles responsive cases.

There is an anti-pattern list too: the old CSF2 Template.bind({}), useState and useEffect logic sitting inside a story, a missing type annotation. The skill tells you to move logic into the component or into play functions. If your project still has CSF2 stories around, expect the skill to flag them.

What it helps with

  • port old CSF2 stories to CSF3
  • cover loading, error and disabled states of a component
  • lift repeated props into meta-level args
  • add decorators and viewport settings for responsive stories
  • fix a Storybook build broken by story edits

Example prompts for the agent

  • Write CSF3 stories for the Button component, include loading and disabled states
  • Convert this stories file from Template.bind({}) to CSF3
  • Add a padding decorator and a mobile viewport to the Input stories

How to install storybook-story-writing

1Skills CLI (Vercel)

npx skills add TheBushidoCollective/han --skill storybook-story-writing -a claude-code

2Manually: put the files in place

~/.claude/skills/storybook-story-writing/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/storybook-story-writing/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about storybook-story-writing

How do I install storybook-story-writing in Claude?

Run npx skills add TheBushidoCollective/han --skill storybook-story-writing -a claude-code. The skill file lands at ~/.claude/skills/storybook-story-writing/SKILL.md.

What does storybook-story-writing do?

It helps you write and modify Storybook stories for components: it enforces CSF3, makes sure component variations are shown, and keeps the build passing.

Which tools can the skill use?

Read, Write, Edit, Bash, Grep and Glob. That covers reading and editing story files, searching the project, and running build commands.

Source text

SKILL.md · Apache-2.0
---
name: storybook-story-writing
user-invocable: false
description: Use when creating or modifying Storybook stories for components. Ensures stories follow CSF3 format, properly showcase component variations, and build successfully.
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash
  - Grep
  - Glob
---

# 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:

```typescript
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<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

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:

```typescript
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<typeof Button>;
```

## Best Practices

### 1. Use TypeScript for Type Safety

```typescript
import type { Meta, StoryObj } from '@storybook/react';

const meta = {
  component: Button,
} satisfies Meta<typeof Button>;

type Story = StoryObj<typeof meta>;
```

### 2. Show All Component States

Create stories for each meaningful state:

```typescript
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',
  },
};
```

### 3. Use Sensible Defaults

```typescript
export const Primary: Story = {
  args: {
    primary: true,
    label: 'Button',
    size: 'medium',
  },
};

// Extend existing stories
export const PrimaryLarge: Story = {
  ...Primary,
  args: {
    ...Primary.args,
    size: 'large',
  },
};
```

### 4. Add Descriptive Parameters

```typescript
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.',
      },
    },
  },
};
```

### 5. Use Decorators for Context

```typescript
import { RouterDecorator } from '../decorators';

const meta = {
  component: Navigation,
  decorators: [
    (Story) => (
      <div style={{ padding: '3rem' }}>
        <Story />
      </div>
    ),
    RouterDecorator,
  ],
} satisfies Meta<typeof Navigation>;
```

## Common Patterns

### Form Components

```typescript
export const EmptyForm: Story = {
  args: {
    onSubmit: (data) => console.log(data),
  },
};

export const PrefilledForm: Story = {
  args: {
    defaultValues: {
      email: '[email protected]',
      name: 'John Doe',
    },
  },
};

export const WithValidationErrors: Story = {
  args: {
    errors: {
      email: 'Invalid email format',
      name: 'Name is required',
    },
  },
};
```

### Layout Components

```typescript
export const WithSidebar: Story = {
  args: {
    sidebar: <Sidebar items={sidebarItems} />,
    children: <Content />,
  },
  parameters: {
    layout: 'fullscreen',
  },
};
```

### Data-Driven Components

```typescript
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

```typescript
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)

```typescript
// Bad - Old CSF2 format
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = { label: 'Button' };
```

```typescript
// Good - CSF3 format
export const Primary: Story = {
  args: { label: 'Button' },
};
```

### ❌ Don't Mix Logic in Stories

```typescript
// Bad
export const Complex: Story = {
  render: (args) => {
    const [state, setState] = useState(false);
    useEffect(() => {
      // Complex side effects
    }, []);
    return <Component {...args} />;
  },
};
```

```typescript
// Good - Move logic to component or use play functions
export const Complex: Story = {
  args: { initialState: false },
};
```

### ❌ Don't Hardcode Repetitive Props

```typescript
// Bad
export const Story1: Story = {
  args: { label: 'Button', size: 'medium', theme: 'light' },
};
export const Story2: Story = {
  args: { label: 'Submit', size: 'medium', theme: 'light' },
};
```

```typescript
// Good - Use meta-level defaults
const meta = {
  component: Button,
  args: {
    size: 'medium',
    theme: 'light',
  },
} satisfies Meta<typeof Button>;

export const Story1: Story = {
  args: { label: 'Button' },
};
export const Story2: Story = {
  args: { label: 'Submit' },
};
```

### ❌ Don't Skip Story Types

```typescript
// Bad - Missing type annotation
export const Primary = {
  args: { label: 'Button' },
};
```

```typescript
// 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

© TheBushidoCollective · Apache-2.0 · TheBushidoCollective/han

Similar skills

Everything for Frontend and UI →

From the same set7

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor