| name | storybook |
| description | Usa esta skill cuando configures o escribas stories en Storybook 8+. Cubre setup para Vite y Next.js, CSF3, controls, play functions, addon de accesibilidad, decorators de providers, integración de tokens de diseño, dark mode y testing visual con test-storybook en CI.
|
Storybook — Configuración y Stories
Flujo de trabajo del agente
- Instalar Storybook y addons según tipo de proyecto (sección Setup).
- Configurar
preview.ts con decorators globales, CSS global y tokens (sección Configuración).
- Escribir stories en CSF3 con
args tipados y tags: ['autodocs'] (sección Estructura CSF3).
- Agregar
play functions a todo componente interactivo (sección Play Functions).
- Verificar que el addon
@storybook/addon-a11y no reporta violaciones AA.
- Cumplir las 10 reglas obligatorias antes de marcar la tarea como completada.
Cross-referencias Obligatorias
Setup
Vite + React
pnpm dlx storybook@latest init --type react --builder vite
pnpm add -D @storybook/addon-a11y @storybook/test @storybook/addon-interactions \
@storybook/addon-themes @storybook/test-runner
Next.js
pnpm dlx storybook@latest init --type nextjs
pnpm add -D @storybook/addon-a11y @storybook/test @storybook/addon-interactions \
@storybook/addon-themes @storybook/test-runner
.storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.{ts,tsx}'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
'@storybook/addon-interactions',
'@storybook/addon-themes',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
};
export default config;
Configuración Global (preview.ts)
import type { Preview } from '@storybook/react';
import { withThemeByDataAttribute } from '@storybook/addon-themes';
import '../src/index.css';
const preview: Preview = {
parameters: {
a11y: {
config: { rules: [{ id: 'color-contrast', enabled: true }] },
},
controls: { matchers: { color: /(background|color)$/i, date: /Date$/ } },
layout: 'centered',
},
decorators: [
withThemeByDataAttribute({
themes: { light: '', dark: 'dark' },
defaultTheme: 'light',
attributeName: 'data-theme',
}),
],
};
export default preview;
Estructura CSF3
Formato estándar para toda story del proyecto:
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentName } from './ComponentName';
const meta = {
title: 'Atoms/ComponentName',
component: ComponentName,
tags: ['autodocs'],
args: {
children: 'Texto de ejemplo',
},
} satisfies Meta<typeof ComponentName>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Disabled: Story = { args: { disabled: true } };
export const Loading: Story = { args: { isLoading: true } };
export const Error: Story = { args: { state: 'error' } };
Convención de title
Atoms/Button → átomo individual
Molecules/FormField → molécula
Organisms/DataTable → organismo
Patterns/AuthModal → patrón de negocio reutilizable
ArgTypes — Controles Interactivos
Storybook infiere controles desde TypeScript. Sobreescribir solo cuando la inferencia no sea clara:
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'ghost', 'destructive'],
description: 'Variante visual del botón',
table: { defaultValue: { summary: 'primary' } },
},
size: { control: 'inline-radio', options: ['sm', 'md', 'lg'] },
isLoading: { control: 'boolean' },
onClick: { action: 'clicked' },
},
Play Functions — Testing Interactivo
Las play functions son tests que corren dentro de Storybook y en CI con test-storybook. Usar los mismos patrones que React Testing Library:
import { expect, userEvent, within } from '@storybook/test';
export const OpensDropdown: Story = {
args: { label: 'Opciones' },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const trigger = canvas.getByRole('button', { name: /opciones/i });
await userEvent.click(trigger);
const menu = canvas.getByRole('menu');
await expect(menu).toBeVisible();
await userEvent.keyboard('{ArrowDown}');
await expect(canvas.getAllByRole('menuitem')[0]).toHaveFocus();
await userEvent.keyboard('{Escape}');
await expect(menu).not.toBeVisible();
await expect(trigger).toHaveFocus();
},
};
export const SubmitsForm: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.type(canvas.getByLabelText(/email/i), 'user@example.com');
await userEvent.type(canvas.getByLabelText(/contraseña/i), 'password123');
await userEvent.click(canvas.getByRole('button', { name: /iniciar sesión/i }));
await expect(canvas.getByText(/bienvenido/i)).toBeInTheDocument();
},
};
Correr en CI
pnpm storybook build
pnpm concurrently -k -s first \
"pnpm http-server storybook-static --port 6006 --silent" \
"pnpm wait-on tcp:6006 && pnpm test-storybook"
A11y Addon
El addon corre axe-core en cada story automáticamente. El panel "Accessibility" muestra las violaciones. Nunca deshabilitar color-contrast — si falla, corregir el token de color.
export const WithException: Story = {
parameters: {
a11y: {
config: {
rules: [
{ id: 'aria-required-parent', enabled: false },
],
},
},
},
};
Decorators de Providers
Providers globales en preview.ts. Providers específicos en el campo decorators de la story.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter } from 'react-router-dom';
import { I18nextProvider } from 'react-i18next';
import i18n from '../src/config/i18n';
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false, staleTime: Infinity } },
});
export const decorators = [
(Story) => (
<MemoryRouter>
<I18nextProvider i18n={i18n}>
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
</I18nextProvider>
</MemoryRouter>
),
];
export const SettingsPage: Story = {
decorators: [
(Story) => (
<MemoryRouter initialEntries={['/dashboard/settings']}>
<Story />
</MemoryRouter>
),
],
};
Tokens de Diseño en Storybook
Importar index.css en preview.ts es suficiente para las CSS custom properties. Para una story de tokens visuales:
import type { Meta, StoryObj } from '@storybook/react';
import { TokensGrid } from './TokensGrid';
export default {
title: 'Design Tokens/Colors',
component: TokensGrid,
tags: ['autodocs'],
} satisfies Meta<typeof TokensGrid>;
Para que el theme de Docs use los mismos colores del DS:
parameters: {
docs: {
theme: {
colorPrimary: 'var(--color-primary)',
colorSecondary: 'var(--color-muted)',
},
},
},
Reglas Obligatorias
tags: ['autodocs'] en todo meta — genera tab Docs con tabla de props y controles.
- Una story por estado semántico —
Default, Disabled, Loading, Error son historias separadas.
play function en todo componente interactivo — dropdowns, modals, formularios, tabs, accordions.
addon-a11y sin violaciones AA — no mergear stories con violaciones activas.
- Nunca deshabilitar
color-contrast — corregir el token de color, no la regla.
- Providers en decorators, nunca en
render — mantener stories declarativas.
- No lógica de negocio en stories — stories visualizan props, no simulan flujos de app.
args compartidos en meta.args — no repetir el mismo arg en cada story.
title sigue jerarquía DS — Atoms/, Molecules/, Organisms/, Patterns/.
- Eventos como
action — registrar onClick, onChange, onSubmit en argTypes para el panel Actions.
Gotchas
- No usar
@storybook/testing-library (deprecated) — migrar a la API unificada @storybook/test de Storybook 8.
document.querySelector en play functions — usar siempre within(canvasElement) para evitar colisiones entre stories.
- CSS global no importado en
preview.ts — los tokens y el reset de Tailwind no se aplican; la story se ve diferente a la app.
autodocs ausente — la tab Docs no aparece para ese componente en el Storybook publicado.
- Providers hardcodeados en
render — dificulta sobreescribir el provider en tests de interacción.
- Test-runner no configurado en CI — las
play functions solo corren localmente; el pipeline no detecta regresiones.