← All posts
·10 min read

Claude Code with Storybook: Component Libraries

Claude CodeStorybookWorkflowDesign System
Claude Code with Storybook: Component Libraries

Why Claude Code changes component library development

Building a component library without AI assistance is slow. You write a component, then write its story, then write its types, then write its tests, then update the documentation. For a library of any size, the ratio of documentation work to actual component logic is at least two-to-one. Most teams skip Storybook stories for internal components, which means the library degrades into undocumented shared code that nobody trusts.

Claude Code changes this ratio. Given a component file and a well-configured CLAUDE.md, Claude generates the story file, the controls configuration, the args definition, the accessibility annotations, and the usage documentation simultaneously. The component file is the source of truth; everything else follows from it.

The constraint is that Claude needs to know your Storybook setup: which version, which renderer (React, Vue, or others), which addons are active, what your story naming convention is, how your design tokens are structured, and what your component API patterns look like. Without this context, Claude generates CSF2-style stories for a CSF3 project, omits the parameters block your theme addon expects, or writes stories that pass props in the wrong shape for your variant system.

If you have not set up Claude Code at all yet, start with the Claude Code setup guide before configuring Storybook-specific behavior.

The Storybook CLAUDE.md

A Storybook project CLAUDE.md needs to declare your setup, naming conventions, story structure patterns, and what to generate alongside every new component.

# Storybook component library rules

## Stack
- Storybook: 8.x
- Renderer: React 19
- Language: TypeScript 5.x, strict mode
- Styling: Tailwind CSS v4 + CSS variables for design tokens
- Component variants: CVA (class-variance-authority)
- Icons: Lucide React
- Build: Vite + storybook-builder-vite

## Addons active
- @storybook/addon-essentials (controls, actions, docs, viewport, backgrounds)
- @storybook/addon-a11y (accessibility checks)
- @storybook/addon-themes (dark/light mode switching)
- storybook-addon-designs (Figma embed)

## File structure
src/
  components/
    Button/
      Button.tsx          # Component
      Button.stories.tsx  # Stories (same directory)
      Button.test.tsx     # Unit tests (Vitest + RTL)
      index.ts            # Named export
  tokens/
    colors.css            # CSS custom properties
    typography.css        # Font scale tokens
    spacing.css           # Spacing tokens
  lib/
    utils.ts              # cn() helper

## Story file conventions
- Stories live next to the component file (not in a __stories__ folder)
- File name: ComponentName.stories.tsx
- Story format: CSF3 (export default, named exports, satisfies Meta<>)
- Every story uses typed args via Meta<typeof Component>
- Every story has a name property
- Default story is named "Default"
- Stories for each variant combination are named by the variant (e.g., "Destructive", "Outline", "Large")

## Hard rules
- Every new component gets a stories file created at the same time
- Every story includes at least: Default, a variant story, a disabled state, and a loading/skeleton state where applicable
- No story should have empty args, args must reflect real usage
- All interactive props go in controls (type: 'select' for variants, type: 'boolean' for flags)
- Story titles use the pattern: "Components/Category/ComponentName"
- Docs page must exist for every component (generated by autodocs in Meta)

The same-time rule is the most important declaration. Without it, Claude will write the component file and stop. With it, Claude generates both files in a single response, which is the entire point of using Claude Code for library development.

The story naming convention (CSF3, typed Meta, named variants) removes the ambiguity about which story format to use. Storybook 8 supports CSF2 and CSF3 simultaneously, and Claude will mix them without guidance.

CSF3 story patterns Claude generates

With the CLAUDE.md above, ask Claude to write a Button component and you get this story file alongside the component:

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

const meta = {
  title: 'Components/Core/Button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: { type: 'select' },
      options: ['default', 'destructive', 'outline', 'ghost', 'link'],
    },
    size: {
      control: { type: 'select' },
      options: ['default', 'sm', 'lg', 'icon'],
    },
    disabled: {
      control: { type: 'boolean' },
    },
    isLoading: {
      control: { type: 'boolean' },
    },
    onClick: { action: 'clicked' },
  },
} satisfies Meta<typeof Button>;

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

export const Default: Story = {
  name: 'Default',
  args: {
    children: 'Button',
    variant: 'default',
    size: 'default',
  },
};

export const Destructive: Story = {
  name: 'Destructive',
  args: {
    children: 'Delete',
    variant: 'destructive',
  },
};

export const Outline: Story = {
  name: 'Outline',
  args: {
    children: 'Cancel',
    variant: 'outline',
  },
};

export const Loading: Story = {
  name: 'Loading',
  args: {
    children: 'Saving',
    isLoading: true,
  },
};

export const Disabled: Story = {
  name: 'Disabled',
  args: {
    children: 'Unavailable',
    disabled: true,
  },
};

export const IconButton: Story = {
  name: 'Icon Only',
  args: {
    children: <TrashIcon className="h-4 w-4" />,
    size: 'icon',
    variant: 'ghost',
    'aria-label': 'Delete item',
  },
};

The satisfies Meta<typeof Button> pattern is CSF3's type-safe story definition. It gives TypeScript inference on argTypes and args without casting. Claude generates this correctly when the convention is declared in CLAUDE.md.

The { action: 'clicked' } configuration in argTypes wires the action addon automatically. Every click in the canvas panel logs to the actions tab. Claude includes this for event handler props when the addon is declared in CLAUDE.md.

For the broader component patterns that underpin a good Storybook library, the Claude Code TypeScript guide covers the type conventions that make stories like this possible.

CVA variant patterns

Class-variance-authority is the standard approach for defining component variants with Tailwind CSS. Claude Code generates CVA configurations correctly when the pattern is in CLAUDE.md.

Add a variants section:

## CVA component pattern

### Button example (defines the shape Claude should follow)
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
        outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        link: 'text-primary underline-offset-4 hover:underline',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-9 rounded-md px-3',
        lg: 'h-11 rounded-md px-8',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
);

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  isLoading?: boolean;
}

### Convention rules
- All variant definitions use CVA
- Component props extend HTML element attributes + VariantProps
- cn() utility merges class names (clsx + tailwind-merge)
- Variants and sizes as named string unions, not boolean flags
- Default variants always specified

This pattern in CLAUDE.md means Claude generates every new component with the CVA setup, correct TypeScript extension from HTML attributes, and the cn() utility. Without it, Claude will mix inline conditional class strings, separate props for each variant, and Tailwind class concatenation that does not handle conflicts correctly.

Design token alignment

The gap between design tokens in code and their representation in Storybook stories is where inconsistency creeps into component libraries. Claude Code can maintain this alignment when the token structure is in CLAUDE.md.

## Design tokens (CSS custom properties in tokens/colors.css)

### Available tokens (use these in stories backgrounds, fill args, etc.)
--background: #ffffff (light) / #0a0a0a (dark)
--foreground: #0a0a0a (light) / #fafafa (dark)
--primary: #18181b
--primary-foreground: #fafafa
--destructive: #ef4444
--muted: #f4f4f5
--muted-foreground: #71717a
--border: #e4e4e7
--ring: #18181b

### Storybook backgrounds config (.storybook/preview.ts)
backgrounds: {
  default: 'light',
  values: [
    { name: 'light', value: '#ffffff' },
    { name: 'dark', value: '#0a0a0a' },
    { name: 'muted', value: '#f4f4f5' },
  ],
},

### Theme addon: use 'light' and 'dark' class on html element

With tokens declared in CLAUDE.md, Claude generates story parameters.backgrounds values that match your actual design tokens instead of generic white and dark. It also adds the correct globals configuration for the themes addon when writing new stories that need dark mode demonstration.

Automated accessibility stories

The @storybook/addon-a11y addon can catch accessibility issues at the story level. Claude Code generates accessibility-aware stories when the addon is declared and the patterns are explicit.

Add to CLAUDE.md:

## Accessibility in stories

### For every interactive component, stories must include:
1. A keyboard-navigable state (tab stops visible)
2. An aria-label example for icon-only variants
3. Sufficient color contrast in all variant combinations

### a11y story pattern
export const AccessibleIconButton: Story = {
  name: 'Icon Only (Accessible)',
  args: {
    children: <SearchIcon className="h-4 w-4" />,
    size: 'icon',
    'aria-label': 'Search',
  },
  parameters: {
    a11y: {
      // Flag if contrast ratio falls below 4.5:1
      config: {
        rules: [{ id: 'color-contrast', enabled: true }],
      },
    },
  },
};

### Hard rules
- Icon-only components always get an aria-label in stories
- Form components always have an associated label story
- Motion/animation stories include a reduced-motion variant

With this in CLAUDE.md, Claude adds parameters.a11y configuration to stories where contrast or interactive labeling is relevant. The icon-only aria-label rule ensures every component that can be used without visible text has a documented accessible usage pattern in the stories.

The Claude Code Tailwind guide covers the Tailwind CSS configuration that integrates with these design tokens.

Generating stories for existing components

One of the most valuable uses of Claude Code for Storybook is retroactively documenting components that were built without stories. Claude can read a component file and generate a complete story file.

Add to CLAUDE.md:

## Story generation from existing components

When generating stories for an existing component:
1. Read the component TypeScript interface first, use all props as argTypes
2. Check for CVA variants, create one story per variant combination
3. Check for loading/error/empty states, create a story for each
4. Check for responsive behavior, add viewport parameter if relevant
5. Always generate at minimum: Default, variant stories, disabled state
6. If the component has complex interaction, add a play() function example

### play() function pattern for interactive stories
import { userEvent, within } from '@storybook/testing-library';

export const OpenDropdown: Story = {
  name: 'Open State',
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const trigger = canvas.getByRole('button', { name: /open menu/i });
    await userEvent.click(trigger);
  },
};

The play() function pattern enables interaction testing directly in Storybook. Claude generates these for components with complex interactions (dropdowns, modals, form validation) when the pattern is in CLAUDE.md. Without guidance, Claude generates static stories only and misses the interaction documentation.

Component documentation patterns

Storybook's autodocs generates a documentation page for every component tagged with 'autodocs'. Claude Code can write the JSDoc comments that populate the description fields in the docs page.

Add to CLAUDE.md:

## JSDoc for autodocs

### Every exported component gets JSDoc:
/**
 * Primary UI button component.
 * Supports five variants, three sizes, loading state, and icon-only usage.
 * Built with CVA and Tailwind CSS. Extends native button element attributes.
 *
 * @example
 * <Button variant="destructive" onClick={handleDelete}>Delete</Button>
 * <Button size="icon" aria-label="Search"><SearchIcon /></Button>
 */

### Every prop interface gets JSDoc:
interface ButtonProps {
  /** Visual style variant. Controls color and border treatment. */
  variant?: 'default' | 'destructive' | 'outline' | 'ghost' | 'link';
  /** Size variant. Controls height, padding, and border radius. */
  size?: 'default' | 'sm' | 'lg' | 'icon';
  /** Shows spinner and disables interaction. */
  isLoading?: boolean;
}

With JSDoc conventions in CLAUDE.md, Claude writes component and prop documentation as part of the component file rather than as an afterthought. The autodocs page then populates with useful descriptions instead of empty prop tables.

What Storybook developers get wrong with Claude Code

Three patterns come up consistently when teams start using Claude Code for component library work.

Asking Claude to write stories before CLAUDE.md is set up. Without the story conventions declared, Claude generates a mix of CSF2 and CSF3 styles, inconsistent naming, and missing controls configuration. The CLAUDE.md setup investment is an hour; the cleanup for inconsistent stories across 50+ components is much longer.

Not declaring all active addons. Claude generates stories that work with the core Storybook build but miss the parameters blocks that addons expect. If the themes addon is active, stories need the globals configuration. If the designs addon is active, stories can embed Figma links. If these addons are not in CLAUDE.md, Claude generates stories that work but do not take advantage of the addon system.

Treating story generation as a one-time task. The real value comes from Claude generating stories as part of every component creation. Declare the "every new component gets a stories file at the same time" rule in CLAUDE.md and enforce it as a non-negotiable. The library documentation stays current with zero extra effort.

Getting more from your Storybook workflow

The configuration in this guide produces a Claude Code setup where every new component ships with typed CSF3 stories, complete controls configuration, accessibility annotations, and JSDoc documentation. The component-to-stories ratio drops from two-to-one to one-to-one, because both files are generated in a single Claude task.

The Claude Code best practices guide covers the broader principle: Claude Code performs at the level of context you provide. A Storybook project with the CLAUDE.md above gets component library output that matches your design system conventions without review cycles spent correcting story format, missing controls, or accessibility gaps.

Start with the stack declaration and story convention sections. Add the CVA pattern and design token alignment once the base story generation is working correctly. If you want Claude Code generating your full component documentation suite, Claudify includes a Storybook CLAUDE.md template pre-configured for Storybook 8, CSF3, CVA, and the standard addon set.

More like this

Ready to upgrade your Claude Code setup?

Get Claudify
Featured on Dofollow.Tools AI Toolz Dir