Use Cases (Atualizado: 02/06/2026)

Desenvolvimento com Storybook e Claude Code: UI, testes e CI

Configure Storybook com Claude Code: stories tipadas, interaction tests, CI e revisão visual com Chromatic.

Desenvolvimento com Storybook e Claude Code: UI, testes e CI

Storybook não é uma vitrine: é um contrato de UI

Storybook permite desenvolver componentes fora da aplicação principal. Para quem está começando, ele funciona como um catálogo onde você abre um botão, formulário ou card de forma isolada. Em projetos reais, o valor é maior: props, estados, interações, acessibilidade, revisão de design e verificações de CI ficam documentados no mesmo lugar.

Se você pedir apenas “adicione Storybook” ao Claude Code, ele pode criar alguns arquivos Button.stories.tsx e parar. Um setup útil precisa dizer quais componentes entram, quais estados são obrigatórios, o que a função play deve testar e como o PR executa Chromatic ou uma validação visual equivalente.

Este guia usa React + TypeScript + Vite com práticas atuais para Storybook 8/9+: .storybook/main.ts, CSF com Meta/StoryObj, satisfies, interaction tests com @storybook/test, @storybook/addon-vitest e CI pronto para Chromatic. As referências oficiais usadas foram main configuration, TypeScript stories, interaction testing, Vitest addon e Chromatic for Storybook.

Se você também está criando um design system, leia junto design system com Claude Code e acessibilidade com Claude Code. Storybook funciona melhor quando protege decisões e fluxos do usuário, não só screenshots.

Três casos para começar

Não leve toda a interface para o Storybook de uma vez. Comece por componentes que mudam com frequência e ficam perto de cadastro, compra ou contato.

CasoEstados no StorybookTrabalho para Claude Code
Button do design systemprimary, secondary, loading, disabled, todos os tamanhosTipos, stories, controls, autodocs
Formulário gratuitovazio, email inválido, envio, sucessoplay, mocks, validação
CTA de artigo ou produtopadrão, destaque, mobile, texto longoResponsivo, diff visual, links internos

Esses três casos já ajudam design, engenharia e negócio a revisar a mesma UI por ângulos diferentes.

Configuração baseada em Vite

Para o Vitest addon, o caminho mais simples é usar um framework Storybook baseado em Vite. Em React + Vite use @storybook/react-vite; em Next.js avalie @storybook/nextjs-vite.

npm create vite@latest storybook-claude-demo -- --template react-ts
cd storybook-claude-demo
npm install
npm create storybook@latest
npx storybook add @storybook/addon-a11y
npx storybook add @storybook/addon-vitest
npm install -D chromatic @vitest/browser-playwright playwright
npx playwright install chromium
{
  "scripts": {
    "dev": "vite",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build",
    "test-storybook": "vitest --project=storybook --run",
    "chromatic": "chromatic"
  }
}
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  framework: '@storybook/react-vite',
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: ['@storybook/addon-docs', '@storybook/addon-a11y', '@storybook/addon-vitest'],
  docs: {
    defaultName: 'Docs',
  },
  staticDirs: ['../public'],
};

export default config;
// .storybook/preview.ts
import type { Preview } from '@storybook/react-vite';

const preview: Preview = {
  tags: ['autodocs'],
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    a11y: {
      test: 'todo',
    },
  },
};

export default preview;

Exemplo 1: Button com stories tipadas

Não crie props só para o Storybook. A story deve refletir o contrato real do componente.

// src/components/Button.tsx
import type { ButtonHTMLAttributes, CSSProperties, ReactNode } from 'react';

type ButtonVariant = 'primary' | 'secondary' | 'outline';
type ButtonSize = 'sm' | 'md' | 'lg';

export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  children: ReactNode;
  variant?: ButtonVariant;
  size?: ButtonSize;
  loading?: boolean;
};

const variantStyle: Record<ButtonVariant, CSSProperties> = {
  primary: { background: '#2563eb', color: '#ffffff', borderColor: '#2563eb' },
  secondary: { background: '#0f172a', color: '#ffffff', borderColor: '#0f172a' },
  outline: { background: '#ffffff', color: '#0f172a', borderColor: '#94a3b8' },
};

const sizeStyle: Record<ButtonSize, CSSProperties> = {
  sm: { minHeight: 32, padding: '0 12px', fontSize: 14 },
  md: { minHeight: 40, padding: '0 16px', fontSize: 15 },
  lg: { minHeight: 48, padding: '0 20px', fontSize: 16 },
};

export function Button({
  children,
  variant = 'primary',
  size = 'md',
  loading = false,
  disabled,
  style,
  ...props
}: ButtonProps) {
  return (
    <button
      {...props}
      disabled={disabled || loading}
      aria-busy={loading || undefined}
      style={{
        ...variantStyle[variant],
        ...sizeStyle[size],
        borderWidth: 1,
        borderStyle: 'solid',
        borderRadius: 6,
        fontWeight: 700,
        cursor: disabled || loading ? 'not-allowed' : 'pointer',
        opacity: disabled ? 0.55 : 1,
        ...style,
      }}
    >
      {loading ? 'Sending...' : children}
    </button>
  );
}
// src/components/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from './Button';

const meta = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
  args: {
    children: 'Start free trial',
    variant: 'primary',
    size: 'md',
  },
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'outline'],
    },
    size: {
      control: 'inline-radio',
      options: ['sm', 'md', 'lg'],
    },
    loading: { control: 'boolean' },
    disabled: { control: 'boolean' },
  },
} satisfies Meta<typeof Button>;

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

export const Primary = {} satisfies Story;

export const Secondary = {
  args: {
    variant: 'secondary',
    children: 'View pricing',
  },
} satisfies Story;

export const AllSizes = {
  render: () => (
    <div style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
      <Button size="sm">Small</Button>
      <Button size="md">Medium</Button>
      <Button size="lg">Large</Button>
    </div>
  ),
} satisfies Story;

export const Loading = {
  args: {
    loading: true,
  },
} satisfies Story;

Exemplo 2: interaction test para formulário

A função play roda depois que a story renderiza. Ela permite validar digitação, erro e envio.

// src/components/SignupForm.tsx
import type { FormEvent } from 'react';
import { useState } from 'react';
import { Button } from './Button';

export type SignupFormData = {
  name: string;
  email: string;
  plan: 'free' | 'team';
};

type SignupFormProps = {
  plan?: SignupFormData['plan'];
  onSubmit?: (data: SignupFormData) => Promise<void> | void;
};

export function SignupForm({ plan = 'free', onSubmit }: SignupFormProps) {
  const [message, setMessage] = useState('');

  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const form = new FormData(event.currentTarget);
    const data: SignupFormData = {
      name: String(form.get('name') ?? ''),
      email: String(form.get('email') ?? ''),
      plan,
    };

    if (!data.email.includes('@')) {
      setMessage('Please enter a valid email address.');
      return;
    }

    await onSubmit?.(data);
    setMessage('Thanks, we will send the setup guide.');
  }

  return (
    <form onSubmit={handleSubmit} style={{ display: 'grid', gap: 12, maxWidth: 360 }}>
      <label>
        Name
        <input name="name" required style={{ display: 'block', width: '100%', minHeight: 36 }} />
      </label>
      <label>
        Email
        <input name="email" type="email" required style={{ display: 'block', width: '100%', minHeight: 36 }} />
      </label>
      <Button type="submit">Get the guide</Button>
      {message ? <p role="status">{message}</p> : null}
    </form>
  );
}
// src/components/SignupForm.stories.tsx
import type { Meta, StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from '@storybook/test';
import { SignupForm } from './SignupForm';

const meta = {
  title: 'Components/SignupForm',
  component: SignupForm,
  args: {
    plan: 'team',
    onSubmit: fn(),
  },
} satisfies Meta<typeof SignupForm>;

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

export const Empty = {} satisfies Story;

export const InvalidEmail = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    await userEvent.type(canvas.getByLabelText('Name'), 'Masa');
    await userEvent.type(canvas.getByLabelText('Email'), 'masa.example.com');
    await userEvent.click(canvas.getByRole('button', { name: 'Get the guide' }));

    await expect(canvas.getByRole('status')).toHaveTextContent('valid email');
  },
} satisfies Story;

export const SuccessfulSubmit = {
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);

    await userEvent.type(canvas.getByLabelText('Name'), 'Masa');
    await userEvent.type(canvas.getByLabelText('Email'), 'masa@example.com');
    await userEvent.click(canvas.getByRole('button', { name: 'Get the guide' }));

    await expect(args.onSubmit).toHaveBeenCalledWith({
      name: 'Masa',
      email: 'masa@example.com',
      plan: 'team',
    });
    await expect(canvas.getByRole('status')).toHaveTextContent('setup guide');
  },
} satisfies Story;

Exemplo 3: Vitest e CI com revisão visual

O Vitest addon transforma stories em testes de componentes no navegador. Stories com play executam interação e assertions.

// .storybook/vitest.setup.ts
import { setProjectAnnotations } from '@storybook/react-vite';
import * as previewAnnotations from './preview';

setProjectAnnotations([previewAnnotations]);
// vitest.config.ts
import { defineConfig, defineProject, mergeConfig } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import viteConfig from './vite.config';

const dirname = path.dirname(fileURLToPath(import.meta.url));

export default mergeConfig(
  viteConfig,
  defineConfig({
    test: {
      projects: [
        defineProject({
          extends: true,
          plugins: [
            storybookTest({
              configDir: path.join(dirname, '.storybook'),
              storybookScript: 'npm run storybook -- --ci',
              storybookUrl: 'http://localhost:6006',
              tags: {
                include: ['test'],
                exclude: ['experimental'],
              },
            }),
          ],
          test: {
            name: 'storybook',
            browser: {
              enabled: true,
              provider: playwright({}),
              headless: true,
              instances: [{ browser: 'chromium' }],
            },
            setupFiles: ['./.storybook/vitest.setup.ts'],
          },
        }),
      ],
    },
  }),
);
# .github/workflows/storybook.yml
name: storybook

on:
  pull_request:

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npm run test-storybook
      - run: npm run build-storybook
      - name: Publish to Chromatic
        if: ${{ secrets.CHROMATIC_PROJECT_TOKEN != '' }}
        run: npx chromatic --project-token=${{ secrets.CHROMATIC_PROJECT_TOKEN }}

Template de pedido para Claude Code

Configure Storybook em um projeto React + TypeScript + Vite.

Escopo:
- src/components/Button.tsx
- src/components/SignupForm.tsx

Requisitos:
- Usar @storybook/react-vite em .storybook/main.ts
- Escrever CSF com Meta/StoryObj e satisfies
- Criar stories de Button para primary, secondary, outline, loading, disabled e todos os tamanhos
- Criar play functions de SignupForm para invalid email e successful submit
- Usar within, userEvent, expect e fn de @storybook/test
- Configurar Vitest addon para rodar test-storybook em CI
- Adicionar build-storybook e uma verificação visual estilo Chromatic no GitHub Actions

Não faça:
- Não use pseudocódigo
- Não adicione props que não existem no componente real
- Não escreva secrets no código
- Não formate arquivos fora do escopo

Ao terminar, informe:
- Arquivos alterados
- Comandos executados
- Riscos restantes

Armadilhas comuns

A primeira é criar props só para Storybook. A segunda é testar apenas o caminho feliz e esquecer email inválido, loading, disabled e envio duplicado. A terceira é deixar datas, valores aleatórios, animações e imagens externas gerarem diffs visuais inúteis. A quarta é misturar @storybook/react, @storybook/react-vite e @storybook/nextjs-vite. A quinta é esquecer Chromium no CI. A sexta é deixar CTAs, cards de produto e formulários de contato para depois, mesmo sendo a parte mais próxima da receita.

CTA e resultado prático

Em mídia técnica ou site de produto, Storybook também protege monetização. Formulários de download gratuito, CTAs de produto, formulários de consultoria e cards de links internos podem virar stories revisadas em cada PR.

Para começar com prompts reutilizáveis, use o cheat sheet gratuito. Para transformar o fluxo em templates, veja produtos e modelos. Para integrar Storybook, CI, Chromatic, acessibilidade e revisão de CTA em um repositório real, use treinamento e consultoria Claude Code.

No teste com um projeto React pequeno, o maior ganho não veio das variações do Button, mas da story de erro do SignupForm. Separar InvalidEmail e SuccessfulSubmit e rodar test-storybook com build-storybook em CI reduziu bastante as correções pedidas ao Claude Code.

#Claude Code #Storybook #desenvolvimento UI #components #frontend
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.