Storybook 8 est la version la plus significative du projet depuis des années. Elle apporte une architecture reconstruite autour de Vite en priorité, une approche de tests entièrement repensée avec des tests d'interaction intégrés, et un support de première classe pour les React Server Components. Combiné à Next.js 15 et son App Router, vous obtenez un workflow puissant pour développer, documenter et tester des composants d'interface utilisateur en total isolation — avant qu'ils ne touchent la moindre page.
Dans ce tutoriel, vous allez configurer Storybook 8 dans un nouveau projet Next.js 15, écrire des stories au format CSF3, tester les interactions des composants, effectuer des vérifications d'accessibilité et générer automatiquement une documentation soignée. À la fin, votre workflow de composants sera entièrement isolé, reproductible et prêt pour l'intégration continue.
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 20 ou une version plus récente installée
- Une connaissance de base de React et TypeScript
- Un projet Next.js fonctionnel (nous en créerons un depuis zéro)
- npm ou pnpm comme gestionnaire de paquets
Ce que vous allez construire
Vous allez construire un composant de système de design simple — un Button — et un composant plus complexe : ProductCard. En chemin, vous apprendrez à :
- Installer et configurer Storybook 8 avec l'adaptateur de framework Next.js
- Écrire des stories CSF3 avec des args typés et des contrôles générés automatiquement
- Ajouter des décorateurs pour simuler les APIs spécifiques à Next.js (Image, Link, Router)
- Écrire des tests d'interaction qui s'exécutent dans Storybook et en CI
- Activer le plugin d'accessibilité (a11y) pour les vérifications WCAG
- Générer de la documentation automatiquement avec
autodocs - Intégrer Chromatic pour les tests de régression visuelle
Étape 1 : Créer un projet Next.js 15
Commencez par scaffolder une nouvelle application Next.js 15 avec TypeScript et Tailwind CSS :
npx create-next-app@latest my-design-system \
--typescript \
--tailwind \
--eslint \
--app \
--src-dir \
--import-alias "@/*"
cd my-design-systemAcceptez toutes les valeurs par défaut. Cela vous donne un projet App Router propre sous src/app/.
Étape 2 : Initialiser Storybook 8
Storybook 8 est livré avec une commande d'initialisation intelligente qui détecte automatiquement votre framework :
npx storybook@latest initLa CLI va :
- Détecter Next.js et installer
@storybook/nextjs(l'adaptateur de framework officiel) - Ajouter les dépendances requises :
storybook,@storybook/react,@storybook/addon-essentials - Générer un répertoire de configuration
.storybook/ - Créer des fichiers de stories d'exemple dans
src/stories/ - Ajouter les scripts
storybooketbuild-storybookàpackage.json
Une fois l'installation terminée, démarrez le serveur de développement :
npm run storybookOuvrez http://localhost:6006 dans votre navigateur. Vous devriez voir l'interface Storybook avec les stories d'exemple.
Étape 3 : Comprendre les fichiers de configuration
Storybook place sa configuration dans .storybook/. Deux fichiers sont essentiels.
.storybook/main.ts — configuration du framework et des plugins :
import type { StorybookConfig } from "@storybook/nextjs";
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"@storybook/addon-a11y",
],
framework: {
name: "@storybook/nextjs",
options: {},
},
staticDirs: ["../public"],
};
export default config;.storybook/preview.ts — décorateurs et paramètres globaux :
import type { Preview } from "@storybook/react";
import "../src/app/globals.css";
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;L'entrée staticDirs indique à Storybook de servir les fichiers depuis public/, ce qui permet à next/image de résoudre correctement les images locales.
Étape 4 : Créer un composant Button
Ajoutez un composant Button réutilisable dans src/components/Button.tsx :
import { ButtonHTMLAttributes } from "react";
import { cva, type VariantProps } from "class-variance-authority";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-blue-600 text-white hover:bg-blue-700",
destructive: "bg-red-600 text-white hover:bg-red-700",
outline: "border border-gray-300 bg-white hover:bg-gray-50 text-gray-900",
ghost: "hover:bg-gray-100 text-gray-900",
},
size: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4 text-base",
lg: "h-12 px-6 text-lg",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
}
);
export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
label: string;
loading?: boolean;
}
export function Button({ label, variant, size, loading, className, ...props }: ButtonProps) {
return (
<button
className={buttonVariants({ variant, size, className })}
disabled={loading || props.disabled}
{...props}
>
{loading && (
<svg className="mr-2 h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
</svg>
)}
{label}
</button>
);
}Installez class-variance-authority si ce n'est pas déjà fait :
npm install class-variance-authorityÉtape 5 : Écrire votre première story au format CSF3
Créez src/components/Button.stories.tsx :
import type { Meta, StoryObj } from "@storybook/react";
import { fn } from "@storybook/test";
import { Button } from "./Button";
const meta = {
title: "Design System/Button",
component: Button,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["default", "destructive", "outline", "ghost"],
},
size: {
control: "select",
options: ["sm", "md", "lg"],
},
loading: { control: "boolean" },
disabled: { control: "boolean" },
},
args: {
onClick: fn(),
label: "Cliquez ici",
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
variant: "default",
size: "md",
},
};
export const Destructive: Story = {
args: {
variant: "destructive",
label: "Supprimer",
},
};
export const Outline: Story = {
args: {
variant: "outline",
label: "Annuler",
},
};
export const Loading: Story = {
args: {
loading: true,
label: "Enregistrement...",
},
};
export const AllSizes: Story = {
render: () => (
<div className="flex items-center gap-4">
<Button label="Petit" size="sm" />
<Button label="Moyen" size="md" />
<Button label="Grand" size="lg" />
</div>
),
};Le pattern satisfies Meta<typeof Button> (CSF3) vous donne une inférence de types complète sur args tout en gardant l'objet meta extensible.
Étape 6 : Utiliser Args et le panneau Controls
En CSF3, les args sont les props en direct passés à votre composant. Le panneau Controls (de addon-essentials) génère automatiquement un formulaire à partir des types TypeScript de votre composant.
Vous pouvez personnaliser les contrôles avec argTypes pour une meilleure expérience :
argTypes: {
variant: {
description: "Style visuel du bouton",
control: { type: "select" },
options: ["default", "destructive", "outline", "ghost"],
table: {
defaultValue: { summary: "default" },
},
},
}Le plugin Actions enregistre les appels onClick en temps réel. Utiliser fn() depuis @storybook/test (et non l'ancien helper action()) signifie que vos tests d'interaction peuvent également espionner ces appels.
Étape 7 : Ajouter des décorateurs pour simuler les APIs Next.js
Les composants Next.js utilisent souvent next/link, next/image ou useRouter. L'adaptateur @storybook/nextjs gère la plupart de cela automatiquement, mais vous aurez peut-être besoin de décorateurs globaux pour des providers comme le thème ou l'i18n.
Modifiez .storybook/preview.ts :
import type { Preview } from "@storybook/react";
import "../src/app/globals.css";
const preview: Preview = {
parameters: {
nextjs: {
appDirectory: true,
},
},
decorators: [
(Story) => (
<div className="p-4">
<Story />
</div>
),
],
};
export default preview;Le paramètre nextjs.appDirectory: true indique à l'adaptateur que vous utilisez l'App Router, débloquant le support de useRouter, usePathname et useSearchParams dans les stories.
Pour simuler une valeur de routeur spécifique par story :
export const ActiveLink: Story = {
parameters: {
nextjs: {
router: {
pathname: "/dashboard",
},
},
},
};Étape 8 : Écrire des tests d'interaction
Les tests d'interaction s'exécutent dans l'interface Storybook et en CI via @storybook/test-runner. Ils utilisent la même API @testing-library/user-event que vous connaissez déjà.
Ajoutez une fonction play à la story Button :
import { expect, userEvent, within } from "@storybook/test";
export const ClickTracking: Story = {
args: {
label: "Soumettre",
onClick: fn(),
},
play: async ({ args, canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button", { name: /soumettre/i });
await userEvent.click(button);
expect(args.onClick).toHaveBeenCalledTimes(1);
},
};Pour exécuter tous les tests d'interaction en mode headless :
npx concurrently -k -s first -n "SB,TEST" \
"npm run storybook -- --quiet" \
"npx wait-on tcp:6006 && npx test-storybook"Installez le test runner :
npm install --save-dev @storybook/test-runner concurrently wait-onÉtape 9 : Tests d'accessibilité avec le plugin a11y
Le plugin @storybook/addon-a11y exécute axe-core automatiquement sur chaque story.
Installez-le :
npm install --save-dev @storybook/addon-a11yAjoutez-le au tableau des addons dans .storybook/main.ts (déjà fait à l'étape 3). Ouvrez n'importe quelle story et cliquez sur l'onglet Accessibility dans le panneau des addons. Les violations apparaissent en rouge, les avertissements en jaune.
Vous pouvez configurer les règles globalement ou par story :
export const ContrastCheck: Story = {
parameters: {
a11y: {
config: {
rules: [
{
id: "color-contrast",
enabled: true,
},
],
},
},
},
};Pour faire échouer les builds CI en cas de violations d'accessibilité, ajoutez ceci à votre config test-runner :
// .storybook/test-runner.ts
import { checkA11y, injectAxe } from "axe-playwright";
import type { TestRunnerConfig } from "@storybook/test-runner";
const config: TestRunnerConfig = {
async preVisit(page) {
await injectAxe(page);
},
async postVisit(page) {
await checkA11y(page, "#storybook-root", {
detailedReport: true,
detailedReportOptions: { html: true },
});
},
};
export default config;Étape 10 : Générer de la documentation automatique avec autodocs
Ajouter tags: ["autodocs"] au meta d'une story génère une page de documentation complète automatiquement. Cette page comprend :
- La description du composant (depuis les commentaires JSDoc sur le composant)
- Un tableau des props avec types, descriptions et valeurs par défaut
- Des exemples interactifs en direct pour chaque story nommée
Ajoutez des commentaires JSDoc aux props de votre composant pour une documentation plus riche :
export interface ButtonProps {
/** Le texte affiché à l'intérieur du bouton */
label: string;
/** Contrôle le style visuel du bouton */
variant?: "default" | "destructive" | "outline" | "ghost";
/** Contrôle la taille du bouton */
size?: "sm" | "md" | "lg";
/** Affiche un spinner et désactive le bouton quand true */
loading?: boolean;
}Vous pouvez aussi écrire de la documentation MDX personnalisée aux côtés de vos stories. Créez src/components/Button.mdx :
import { Canvas, Meta } from "@storybook/blocks";
import * as ButtonStories from "./Button.stories";
<Meta of={ButtonStories} />
# Button
Le composant `Button` gère toutes les interactions utilisateur principales.
Utilisez la prop `variant` pour communiquer l'intention et `size` pour
s'adapter à la mise en page environnante.
<Canvas of={ButtonStories.Default} />
## Quand utiliser chaque variante
- **default** — actions principales, dialogues de confirmation
- **destructive** — supprimer, retirer, actions irréversibles
- **outline** — actions secondaires aux côtés d'un bouton principal
- **ghost** — actions tertiaires dans des espaces réduits ou barres d'outilsÉtape 11 : Construire Storybook pour l'hébergement statique
Générez un build statique pour l'héberger sur n'importe quel CDN :
npm run build-storybookLe résultat est dans storybook-static/. Vous pouvez le déployer sur Vercel, Netlify ou GitHub Pages. Ajoutez-le à votre pipeline CI pour que chaque PR inclue une prévisualisation mise à jour des composants.
Pour GitHub Actions :
name: Storybook CI
on: [push, pull_request]
jobs:
storybook:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run build-storybook
- run: npx concurrently -k -s first -n "SB,TEST"
"npx http-server storybook-static --port 6006 --silent"
"npx wait-on tcp:6006 && npx test-storybook --url http://localhost:6006"Étape 12 : Tests de régression visuelle avec Chromatic
Chromatic est le service cloud créé par l'équipe Storybook pour les tests de régression visuelle. Il capture des instantanés pixel par pixel de chaque story et vous alerte des différences visuelles dans les pull requests.
Installez la CLI :
npm install --save-dev chromaticExécutez votre premier build pour établir une baseline :
npx chromatic --project-token=YOUR_TOKENObtenez votre token de projet sur chromatic.com. Après le premier build, chaque push en CI sera comparé à la baseline acceptée.
Ajoutez à GitHub Actions :
- name: Publish to Chromatic
uses: chromaui/action@latest
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}Résolution des problèmes courants
Storybook échoue au démarrage après une mise à jour de Next.js
Exécutez npx storybook upgrade pour mettre à jour tous les packages Storybook vers les dernières versions compatibles.
useRouter lance une erreur "invariant expected app router"
Ajoutez nextjs: { appDirectory: true } aux paramètres de .storybook/preview.ts comme indiqué à l'étape 7.
Les styles Tailwind ne se chargent pas dans Storybook
Assurez-vous d'importer globals.css en haut de .storybook/preview.ts. Le chemin doit être relatif au répertoire .storybook/.
Images cassées avec next/image
Ajoutez staticDirs: ["../public"] à .storybook/main.ts. Les images placées dans public/ seront servies par le serveur de développement Storybook aux mêmes chemins.
Les tests d'interaction expirent
Augmentez le timeout dans votre config test-runner ou vérifiez les mises à jour d'état asynchrones non attendues dans la fonction play.
Prochaines étapes
- Explorez @storybook/addon-viewport pour tester les mises en page responsive sur différentes tailles d'écran
- Utilisez MSW (Mock Service Worker) pour simuler des appels API dans les stories avec
msw-storybook-addon - Ajoutez Storybook Test comme plugin Vitest pour exécuter les tests d'interaction dans votre suite Vitest existante
- Lisez le guide de migration Storybook 8 si vous mettez à niveau depuis la version 7
Conclusion
Storybook 8 fait du développement orienté composants une partie de premier ordre du workflow Next.js 15. Vous disposez maintenant d'une configuration qui :
- Développe les composants en total isolation, libre des préoccupations de niveau page
- Documente chaque composant avec des exemples interactifs en direct
- Détecte les régressions d'accessibilité avant qu'elles n'atteignent les utilisateurs
- Exécute des tests d'interaction en CI en utilisant les mêmes assertions que vos tests unitaires
- Détecte les régressions visuelles grâce aux instantanés Chromatic
Ce workflow s'adapte d'un simple Button à un système de design d'entreprise avec des centaines de composants.