Front-end

- Storybook et Angular : documenter ses composants

Angular Storybook Design-System Documentation Composants Csf3 Controls Autodocs Tests-Interaction Accessibilite Chromatic Regression-Visuelle Front-End Bonnes-Pratiques
Storybook et Angular : documenter ses composants

Documentez et testez vos composants Angular avec Storybook : stories CSF3, controls, autodocs, tests d'interaction, addon a11y et déploiement Chromatic.

Pourquoi Storybook ?

Quand un projet Angular grandit, ses composants se retrouvent enfouis dans des dizaines de routes et de conditions métier. Pour voir à quoi ressemble un bouton en état loading, une carte produit en rupture de stock ou une modale d'erreur, il faut souvent naviguer, se connecter, remplir un formulaire et déclencher un scénario improbable. Storybook renverse cette logique : il vous permet de développer, visualiser et documenter chaque composant de façon isolée, en dehors de l'application.

En bref : Storybook est un atelier de composants. Chaque « story » décrit un état précis d'un composant (variantes, props, cas limites). Vous obtenez un catalogue navigable, une documentation qui ne se périme jamais, et une base pour les tests d'interaction et de régression visuelle.

Storybook n'est pas spécifique à Angular : il fonctionne avec React, Vue, Web Components, Svelte et bien d'autres. Pour Angular, le package @storybook/angular s'appuie sur le builder officiel et comprend les composants standalone, les modules, l'injection de dépendances et les Signals. Trois bénéfices concrets ressortent en équipe :

  • Développement isolé : vous construisez un composant sans lancer toute l'application ni brancher une API. Le cycle de feedback tombe à quelques secondes.
  • Design system vivant : designers et développeurs partagent une source de vérité unique. Chaque variante de composant est cataloguée, versionnée et cliquable.
  • Documentation automatique : les inputs, outputs et types de vos composants sont extraits et affichés sans effort, toujours à jour avec le code.

Là où un fichier README.md se désynchronise du code dès la première refactorisation, une story reste alignée : elle importe réellement le composant et le rend. Si l'API change, la story casse au build, et vous le savez immédiatement.

Besoin Sans Storybook Avec Storybook
Voir un état rare Reproduire un scénario complet Ouvrir la story dédiée
Documenter les inputs Rédiger et maintenir à la main Autodocs généré depuis les types
Tester une interaction Spec Jasmine + TestBed Play function dans la story
Détecter une régression visuelle Revue manuelle des captures Chromatic en CI

Dans la suite de ce guide, nous partons d'un projet Angular vierge, installons Storybook, écrivons des stories au format moderne CSF3, ajoutons de la documentation, des tests d'interaction, un addon d'accessibilité, puis nous branchons le tout en CI avec Chromatic.

Installer Storybook dans un projet Angular

L'installation se fait via un unique CLI d'initialisation qui détecte automatiquement votre framework. Depuis la racine d'un projet Angular existant (créé avec ng new), lancez la commande suivante :

# Détecte Angular, installe les dépendances et scaffold .storybook/
npx storybook@latest init

# Puis démarrer le serveur de développement Storybook
npm run storybook

Le CLI effectue plusieurs actions automatiquement : il installe @storybook/angular et ses dépendances, crée le dossier de configuration .storybook/, ajoute des stories d'exemple dans src/stories/, et branche deux scripts npm. Après quelques secondes, Storybook s'ouvre sur http://localhost:6006.

Structure générée dans le projet :

# Arborescence après l'init
.storybook/
├── main.ts          # Config : emplacement des stories, addons, framework
├── preview.ts       # Config globale du rendu (decorators, parameters)
├── tsconfig.json    # TS config dédiée à Storybook
src/stories/         # Stories d'exemple (Button, Header, Page...)

Le fichier central est .storybook/main.ts. Il déclare où trouver les stories, quels addons activer et quel framework utiliser :

// .storybook/main.ts
import type { StorybookConfig } from '@storybook/angular';

const config: StorybookConfig = {
    // Où Storybook cherche les fichiers de stories
    stories: ['../src/**/*.stories.@(ts|mdx)'],
    // Addons : docs, controls, actions, accessibilité...
    addons: [
        '@storybook/addon-essentials', // controls, actions, viewport, docs...
        '@storybook/addon-a11y',       // vérification d'accessibilité
        '@storybook/addon-interactions' // panneau des play functions
    ],
    framework: {
        name: '@storybook/angular',
        options: {}
    },
    // Active la génération automatique de documentation
    docs: { autodocs: 'tag' }
};

export default config;

Le fichier .storybook/preview.ts configure le rendu global : thèmes, décorateurs appliqués à toutes les stories, ou encore le matching automatique des controls selon le nom des propriétés.

// .storybook/preview.ts
import type { Preview } from '@storybook/angular';

const preview: Preview = {
    parameters: {
        // Détecte automatiquement les controls color et date
        controls: {
            matchers: {
                color: /(background|color)$/i,
                date: /Date$/i
            }
        }
    }
};

export default preview;
Note : si vous utilisez un projet Nx ou une architecture monorepo, l'init reste le même mais adaptez le chemin stories dans main.ts pour pointer vers vos bibliothèques (ex. ../libs/**/*.stories.ts). Le builder Angular est déjà partagé par les projets.

Écrire sa première story au format CSF3

Le Component Story Format 3 (CSF3) est le format recommandé depuis Storybook 7. Une story y est un simple objet, plus une fonction template comme en CSF2. Prenons un composant bouton standalone pour illustrer :

// button.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'app-button',
    standalone: true,
    template: `
        <button
            [class]="'btn btn-' + variant"
            [disabled]="disabled"
            (click)="clicked.emit($event)">
            {{ label }}
        </button>
    `
})
export class ButtonComponent {
    @Input() label = 'Bouton';
    @Input() variant: 'primary' | 'secondary' | 'danger' = 'primary';
    @Input() disabled = false;
    @Output() clicked = new EventEmitter<MouseEvent>();
}

La story associée se place dans un fichier button.stories.ts, à côté du composant. Elle commence par un objet Meta typé, qui décrit le composant et sert de configuration par défaut :

// button.stories.ts
import type { Meta, StoryObj } from '@storybook/angular';
import { ButtonComponent } from './button.component';

// Meta typé : titre dans l'arbo, composant cible, tags
const meta: Meta<ButtonComponent> = {
    title: 'UI/Button',        // Chemin dans la sidebar Storybook
    component: ButtonComponent, // Le composant Angular rendu
    tags: ['autodocs'],        // Génère une page de doc automatique
    args: {                    // Valeurs par défaut partagées
        label: 'Cliquez-moi',
        variant: 'primary',
        disabled: false
    }
};

export default meta;

// Chaque export nommé = une story (un état du composant)
type Story = StoryObj<ButtonComponent>;

export const Primary: Story = {}; // Hérite des args du meta

export const Secondary: Story = {
    args: { variant: 'secondary', label: 'Secondaire' }
};

export const Danger: Story = {
    args: { variant: 'danger', label: 'Supprimer' }
};

export const Disabled: Story = {
    args: { disabled: true, label: 'Indisponible' }
};

Chaque export nommé devient une entrée dans la barre latérale de Storybook. Le StoryObj<ButtonComponent> apporte l'autocomplétion : votre IDE connaît les args disponibles (label, variant, disabled) et refuse une valeur invalide comme variant: 'blue'.

Comparons les deux formats pour bien mesurer le gain de CSF3 :

Aspect CSF2 (ancien) CSF3 (recommandé)
Définition d'une story Fonction + .bind({}) Objet StoryObj simple
Args Story.args = {...} après bind Clé args dans l'objet
Typage Partiel, verbeux Inféré via Meta<T>
Verbosité Élevée Minimale
Astuce : nommez vos stories selon les états métier, pas selon les valeurs techniques. Disabled, Loading, EmptyState parlent davantage à l'équipe que Variant3. Ces noms deviennent aussi les libellés de la doc générée.

Args, controls et actions

Les args sont les entrées d'une story. Storybook les transforme automatiquement en controls : des widgets interactifs (champ texte, sélecteur, interrupteur) affichés dans le panneau du bas. Vous modifiez un input en direct et le composant se met à jour, sans recompiler.

Vous pouvez affiner chaque control via argTypes. Par exemple, transformer la propriété variant en menu déroulant plutôt qu'en champ texte libre :

// button.stories.ts — personnaliser les controls
const meta: Meta<ButtonComponent> = {
    title: 'UI/Button',
    component: ButtonComponent,
    tags: ['autodocs'],
    argTypes: {
        // Menu déroulant pour l'input variant
        variant: {
            control: 'select',
            options: ['primary', 'secondary', 'danger'],
            description: 'Style visuel du bouton'
        },
        // Interrupteur booléen pour disabled
        disabled: { control: 'boolean' },
        // Masquer un input du panneau de controls
        clicked: { control: false }
    }
};

Pour les outputs (les @Output / EventEmitter), Storybook propose les actions : chaque événement émis est journalisé dans l'onglet Actions. Avec l'addon essentials, il suffit de suivre la convention de nommage on* ou de déclarer l'action explicitement :

// Logger les événements émis par le composant
import { action } from '@storybook/addon-actions';

export const Interactive: Story = {
    args: {
        label: 'Envoyer',
        // Chaque clic apparaît dans l'onglet Actions
        clicked: action('button-clicked') as any
    }
};

Les args ne sont pas figés à une seule story : on peut les définir à trois niveaux, du plus général au plus spécifique. Storybook fusionne ensuite ces sources dans un ordre de priorité clair.

// Ordre de priorité des args (du plus faible au plus fort) :
// 1. preview.ts       -> args globaux (toutes les stories)
// 2. meta.args        -> args par défaut du composant
// 3. story.args       -> args spécifiques à une story

// Exemple : globalTypes dans preview.ts pour un thème
export const globalTypes = {
    theme: {
        description: 'Thème global',
        defaultValue: 'light',
        toolbar: {
            icon: 'circlehollow',
            items: ['light', 'dark']
        }
    }
};
Note : les controls sont générés à partir des types TypeScript de vos @Input. Un @Input() count: number reçoit automatiquement un control numérique, un boolean un interrupteur, une union de littéraux un menu radio. Bien typer vos composants améliore donc directement la qualité de la documentation.

Documenter avec autodocs et MDX

Storybook génère une page de documentation par composant dès que vous ajoutez le tag 'autodocs' au Meta. Cette page rassemble automatiquement une description, un tableau des props (issu des types et des JSDoc), et un aperçu de chaque story avec ses controls.

// Activer autodocs pour un composant
const meta: Meta<ButtonComponent> = {
    title: 'UI/Button',
    component: ButtonComponent,
    // Ce tag déclenche la génération d'une page "Docs"
    tags: ['autodocs'],
    parameters: {
        docs: {
            description: {
                component: 'Bouton réutilisable du design system. ' +
                    'Supporte trois variantes et un état désactivé.'
            }
        }
    }
};

Les descriptions de chaque input proviennent des commentaires JSDoc placés au-dessus de vos @Input. Documenter le composant Angular documente donc directement Storybook :

// button.component.ts — les JSDoc alimentent la doc
export class ButtonComponent {
    /** Texte affiché à l'intérieur du bouton */
    @Input() label = 'Bouton';

    /** Style visuel : primary, secondary ou danger */
    @Input() variant: 'primary' | 'secondary' | 'danger' = 'primary';

    /** Désactive le bouton et bloque les clics */
    @Input() disabled = false;
}

Pour une documentation plus riche — guide d'usage, exemples narratifs, do & don't — utilisez le format MDX. Un fichier .mdx mêle du Markdown et des blocs Storybook comme <Meta>, <Story> ou <Canvas> :

{/* Button.mdx — documentation manuelle enrichie */}
import { Meta, Canvas, Controls } from '@storybook/blocks';
import * as ButtonStories from './button.stories';

<Meta of={ButtonStories} />

# Bouton

Le composant **Button** est la brique de base de nos formulaires.
Utilisez la variante `primary` pour l'action principale d'un écran,
et une seule par vue.

## Aperçu interactif

<Canvas of={ButtonStories.Primary} />

## Propriétés

<Controls />
Bonne pratique : réservez MDX aux composants « publics » de votre design system, ceux que d'autres équipes consomment. Pour les composants internes, l'autodocs suffit largement et évite de maintenir de la prose en double.

Tester : play functions, a11y et test-runner

Une story ne se contente pas d'afficher un composant : elle peut aussi le piloter. Une play function est une fonction asynchrone qui s'exécute après le rendu et simule des interactions utilisateur. Elle s'appuie sur @storybook/test, qui expose userEvent, within et expect.

// button.stories.ts — test d'interaction dans une story
import { within, userEvent, expect } from '@storybook/test';

export const ClickInteraction: Story = {
    args: { label: 'Valider', variant: 'primary' },
    // La play function s'exécute après le rendu du composant
    play: async ({ canvasElement, args }) => {
        const canvas = within(canvasElement);
        // Récupérer le bouton par son rôle accessible
        const button = canvas.getByRole('button', { name: /valider/i });

        // Simuler un clic utilisateur
        await userEvent.click(button);

        // Vérifier que le bouton est bien présent et actif
        await expect(button).toBeEnabled();
    }
};

Ces tests s'exécutent visiblement dans le navigateur : l'onglet Interactions déroule chaque étape (clic, assertion) et vous pouvez rejouer le scénario pas à pas. C'est précieux pour déboguer un composant de formulaire complexe.

Pour l'accessibilité, l'addon @storybook/addon-a11y analyse chaque story avec le moteur axe-core et signale les violations (contraste insuffisant, libellé manquant, rôle ARIA incorrect) dans un panneau dédié. On peut aussi le configurer story par story :

// preview.ts — configurer les règles d'accessibilité
const preview: Preview = {
    parameters: {
        a11y: {
            // Éléments à analyser
            element: '#storybook-root',
            config: {
                rules: [
                    // Désactiver ponctuellement une règle si justifié
                    { id: 'color-contrast', enabled: true }
                ]
            }
        }
    }
};

Enfin, le test-runner (@storybook/test-runner, basé sur Playwright) exécute toutes vos stories en headless. Chaque story sans erreur de rendu passe ; chaque play function devient une assertion vérifiée ; chaque violation a11y bloquante peut faire échouer le build. Parfait pour la CI :

# Installer et lancer le test-runner
npm install --save-dev @storybook/test-runner

# Lancer Storybook en headless puis exécuter les tests
npm run storybook -- --ci & npx test-storybook

# Ou via un script package.json
# "test-storybook": "test-storybook"
  • Une play function par interaction critique (soumission, ouverture de modale)
  • Sélecteurs par rôle accessible (getByRole) plutôt que par classe CSS
  • Addon a11y activé et violations bloquantes traitées
  • Test-runner branché dans la pipeline CI
  • Stories couvrant les états limites (vide, erreur, chargement)

Intégrer un design system Angular

Vos composants ne vivent jamais seuls : ils dépendent de thèmes, de providers (services, HttpClient, routeur) et parfois d'une librairie tierce comme Angular Material ou PrimeNG. Storybook gère tout cela grâce aux decorators et à applicationConfig.

Un decorator enveloppe la story pour lui fournir un contexte : un conteneur thématisé, un padding, un provider. On l'applique globalement dans preview.ts ou localement sur une story :

// preview.ts — decorator global pour appliquer un thème
import { moduleMetadata } from '@storybook/angular';

const preview: Preview = {
    decorators: [
        // Envelopper chaque story dans un conteneur thématisé
        (storyFn, context) => {
            const theme = context.globals['theme'] || 'light';
            return {
                ...storyFn(),
                template: `<div class="theme-${theme} p-4">
                    ${storyFn().template ?? '<story></story>'}
                </div>`
            };
        }
    ]
};

Pour injecter des providers (services partagés, configuration standalone), utilisez applicationConfig — l'équivalent Storybook de bootstrapApplication :

// button.stories.ts — fournir des providers à une story
import { applicationConfig, moduleMetadata } from '@storybook/angular';
import { provideHttpClient } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
import { ThemeService } from '../services/theme.service';

const meta: Meta<ButtonComponent> = {
    title: 'UI/Button',
    component: ButtonComponent,
    decorators: [
        // Providers de niveau application (standalone)
        applicationConfig({
            providers: [
                provideHttpClient(),
                provideAnimations(),
                ThemeService
            ]
        }),
        // Importer des composants ou modules complémentaires
        moduleMetadata({
            imports: [/* autres composants standalone */]
        })
    ]
};

Si vous consommez une librairie de composants (Angular Material, PrimeNG, un design system maison publié en package), l'approche reste identique : importez le module ou le composant standalone via moduleMetadata, et chargez le thème CSS de la librairie dans preview.ts ou via l'option styles du builder Angular dans main.ts.

Note : pour un design system publié en bibliothèque Angular (ng generate library), placez les stories à l'intérieur de la lib. Elles voyagent ainsi avec le composant et documentent le package pour toutes les applications qui le consomment. Storybook devient la vitrine officielle de votre système de design.

CI, régression visuelle et déploiement

Storybook prend toute sa valeur en intégration continue. Trois usages se combinent : construire la doc statique, tester les stories en headless, et détecter les régressions visuelles avec Chromatic, le service développé par l'équipe Storybook.

D'abord, la build statique. La commande build-storybook génère un site HTML/JS autonome, déployable sur n'importe quel hébergeur statique (Netlify, GitHub Pages, S3, votre CDN) :

# Générer la doc statique dans ./storybook-static
npm run build-storybook

# Résultat : un dossier prêt à déployer
# storybook-static/index.html + assets

Ensuite, la régression visuelle avec Chromatic. À chaque commit, Chromatic prend une capture de chaque story et la compare à la version de référence. Toute différence de pixels (couleur, espacement, police) est signalée pour validation humaine :

# Installer Chromatic et publier les stories
npm install --save-dev chromatic

# Publier avec le token de projet (récupéré sur chromatic.com)
npx chromatic --project-token=<votre-token>

Enfin, on assemble le tout dans un workflow CI (ici GitHub Actions) qui installe, teste et publie à chaque push :

# .github/workflows/storybook.yml
name: Storybook
on: [push]

jobs:
    chromatic:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v4
              with:
                  fetch-depth: 0 # Chromatic a besoin de l'historique git
            - uses: actions/setup-node@v4
              with:
                  node-version: 20
            - run: npm ci
            # Publier les stories et lancer la régression visuelle
            - uses: chromaui/action@latest
              with:
                  projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

Le token Chromatic est stocké dans les secrets du dépôt, jamais en clair dans le code. À chaque Pull Request, l'équipe reçoit un lien vers les changements visuels détectés et les approuve ou les rejette en un clic. Une régression involontaire (un padding cassé, un contraste perdu) ne passe plus inaperçue.

Récapitulons le pipeline complet d'un projet Angular + Storybook mature :

Étape Commande Rôle
Développement npm run storybook Atelier local sur :6006
Tests d'interaction npx test-storybook Play functions + a11y en headless
Régression visuelle npx chromatic Diff pixel des stories
Documentation npm run build-storybook Site statique déployable
  • Storybook installé via npx storybook@latest init
  • Stories au format CSF3 (Meta + StoryObj typés)
  • Tag autodocs sur les composants publics
  • Play functions pour les interactions critiques
  • Addon a11y activé et violations traitées
  • Test-runner branché en CI
  • Chromatic pour la régression visuelle sur chaque PR
  • Doc statique déployée et partagée avec l'équipe design

En conclusion, Storybook transforme la façon dont une équipe Angular construit son interface : les composants se développent en isolation, se documentent tout seuls, se testent au niveau de l'interaction et se protègent contre les régressions visuelles. Le format CSF3 rend les stories concises et typées, l'autodocs élimine la documentation qui se périme, et Chromatic ferme la boucle en CI. Commencez petit — une story sur votre bouton — puis étendez le catalogue composant par composant. En quelques semaines, Storybook devient la source de vérité vivante de votre design system.

Partager