Esc
 Naviguer  Ouvrir Esc Fermer
Aller au contenu

7.6 — Design systems & UI kits

🎯 Objectif : ne pas réinventer chaque dropdown, chaque modal, chaque tooltip. Construire une UI cohérente avec Tailwind et des composants accessibles éprouvés.

À l'issue de cet axe, tu sauras :

  • Comprendre la philosophie utility-first de Tailwind CSS
  • Distinguer un UI kit (MUI, Chakra) d'un kit headless (Radix, Headless UI)
  • Utiliser shadcn/ui pour avoir des composants beaux et accessibles dans son projet
  • Documenter ses composants avec Storybook
  • Structurer un design system maison (tokens → primitives → composants)

Débutant 10 min prérequis : axe 6 lu

Sans design system :

  • Bouton bleu A à 5 nuances différentes selon les pages.
  • Modale réinventée 4 fois, chacune avec son bug d’accessibilité.
  • 3 mois pour livrer une nouvelle feature parce que tout est custom.

Avec un design system :

  • 1 token de couleur primary, 1 composant Button, 1 modale a11y.
  • Cohérence visuelle automatique.
  • Vélocité produit qui décolle.

Au lieu d’écrire un fichier CSS, tu styles directement dans la classe HTML avec des utilities :

<button className="bg-blue-500 hover:bg-blue-700 text-white font-semibold px-4 py-2 rounded-md transition">
Click me
</button>
ArgumentExplication
Pas de namingPlus de .btn-primary à inventer. Plus de BEM.
Pas de CSS séparéTu vois le style en regardant le composant.
Cohérence par défauttext-base, text-lg etc. sont une échelle, pas des valeurs arbitraires.
Bundle minimalisteTailwind purge ce qui n’est pas utilisé → CSS final ~10 Ko.
DX excellenteAuto-completion via Tailwind IntelliSense (VS Code).
Fenêtre de terminal
npm install -D tailwindcss @tailwindcss/vite postcss autoprefixer
vite.config.ts
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [tailwindcss()],
});
src/styles.css
@import 'tailwindcss';
<div className="
text-sm md:text-base lg:text-lg /* responsive font */
p-2 sm:p-4 md:p-6 /* padding responsive */
bg-gray-100 dark:bg-gray-800 /* mode sombre auto */
hover:bg-gray-200 /* état hover */
transition-colors /* transition */
">
CatégorieExemple
Spacingp-1 (4px), p-2 (8px), p-4 (16px), p-8 (32px)
Couleurstext-blue-500, bg-red-100, border-gray-200
Tailles textetext-xs, text-sm, text-base, text-lg, text-xl
Layoutflex, grid, block, hidden
Responsivepréfixes sm:, md:, lg:, xl:, 2xl:
Étatshover:, focus:, active:, disabled:, group-hover:

HTML chargé : <button class="...10 classes...">. Lisibilité affectée. ❌ Courbe d’apprentissage : il faut mémoriser les noms (mais l’autocompletion aide énormément).

→ Solutions :

  • Composants encapsulés : tu écris <Button variant="primary"> une fois, l’utility-first est dans le composant.
  • @apply (à utiliser parcimonieusement) : créer une classe maison agrégeant des utilities.

UI kits classiques (Material UI, Chakra, Mantine, Bootstrap React)

Section intitulée « UI kits classiques (Material UI, Chakra, Mantine, Bootstrap React) »
// MUI
import { Button } from '@mui/material';
<Button variant="contained" color="primary">Click</Button>

✅ Composants prêts à l’emploi, beaucoup d’options. ❌ Lock-in visuel : tous les sites MUI se ressemblent. ❌ Customisation vite pénible (overriding de styles). ❌ Bundle souvent gros.

Verdict 2026 : MUI/Chakra restent valables pour des dashboards internes ou MVP rapides. Pour un produit avec design custom, on préfère les kits headless.

Idée : fournir le comportement (focus trap, ARIA, clavier, animations) sans imposer le style.

import * as Dialog from '@radix-ui/react-dialog';
<Dialog.Root>
<Dialog.Trigger className="btn-primary">Ouvrir</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50" />
<Dialog.Content className="fixed top-1/2 left-1/2 ...">
<Dialog.Title>Titre</Dialog.Title>
<Dialog.Description>Description.</Dialog.Description>
<Dialog.Close>Fermer</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>

✅ Accessibilité béton (focus trap, ESC pour fermer, ARIA). ✅ Aucun style imposé — tu utilises Tailwind ou ce que tu veux. ✅ Le composant ne casse pas quand tu changes la couleur.

Composants couverts : Dialog, Dropdown, Select, Tabs, Tooltip, Popover, Accordion, Toast, etc.

shadcn/ui n’est pas une lib npm classique. C’est un catalogue de composants que tu copies dans ton projet.

Fenêtre de terminal
npx shadcn@latest init
npx shadcn@latest add button
# ↓ ajoute components/ui/button.tsx dans TON projet

Le code est dans ton repo, tu peux le modifier librement. shadcn fournit :

  • Tailwind CSS pour le style.
  • Radix UI pour le comportement.
  • TypeScript strict.
  • Modules cohérents (Button, Card, Dialog, Form, Table…).

✅ Tu possèdes le code (pas de breaking change forcé par une lib externe). ✅ Customisation illimitée. ✅ Accessible et beau par défaut. ✅ Évolue avec ton projet.

❌ Tu dois maintenir les composants quand shadcn met à jour (mais ce n’est pas obligatoire).

import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Ouvrir</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirmation</DialogTitle>
</DialogHeader>
<p>Êtes-vous sûr ?</p>
</DialogContent>
</Dialog>

C’est devenu le standard de fait pour démarrer un projet React/Next moderne en 2025-2026.

3 niveaux à structurer :

Variables de design (couleurs, espacements, rayons, typographie). Voir 3.6 et 5.2.

:root {
--color-primary: #2563eb;
--color-primary-hover: #1d4ed8;
--space-md: 1rem;
--radius-md: 0.5rem;
}

Tailwind a son tailwind.config.js pour les exposer comme utilities.

Composants de base : Button, Input, Select, Card, Dialog. Souvent ce que shadcn/ui te fournit.

Composants métier qui composent des primitives :

  • ProductCard (Card + Button + Image)
  • LoginForm (Form + Input + Button)
  • CommentList (Card multiple + état)

C’est le plus spécifique à ton produit. Mais ça reste réutilisable dans toute l’app.

Storybook génère un site qui montre tous les états de tous tes composants, isolés.

Fenêtre de terminal
npx storybook@latest init
Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
component: Button,
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: { variant: 'primary', children: 'Click me' },
};
export const Disabled: Story = {
args: { variant: 'primary', disabled: true, children: 'Disabled' },
};

✅ Documentation toujours à jour (le code et la doc sont le même fichier). ✅ Tester chaque variant en isolation. ✅ Designers et devs partagent le même langage.

❌ Maintenance non triviale sur gros projets.

Verdict : très utile pour les design systems d’entreprise (équipes 10+ devs, designers dédiés). Surdimensionné pour un side-project ou un MVP.

Si tu…Choisis
Veux livrer vite avec un design okshadcn/ui + Tailwind
Construis un dashboard interneMantine, Chakra, ou MUI
Bâtis un produit avec design customTailwind + Radix
As un designer dédié et budget tempsTailwind + Radix + Storybook + tokens custom
Veux du CSS classique sans frameworkPico CSS, classless CSS, ou CSS modules
Tu démarres un MVP en Next.js. Tu veux des composants beaux, accessibles, modifiables. Choix idéal ?
Critique fréquente de Tailwind : 'le HTML est illisible avec 15 classes'. Solution ?
Différence majeure entre Radix UI et Material UI ?

Fin de l’axe 7. Direction l’axe 8 — Backend (multi-langages), ou attaque le projet « Dashboard Next.js » dans exercises/07-frameworks-frontend/.