Intermédiaire 20 min de lecture · 4 246 mots

Systèmes de design : Créer et maintenir une design library

Systèmes de design : Créer et maintenir une design library
Intermédiaire · 2026.05.09

Introduction

Un système de design (design system) est bien plus qu’une simple bibliothèque de composants. C’est un langage visuel unifié, un ensemble de règles, de principes et de composants réutilisables qui garantissent la cohérence à travers tous vos produits digitaux.

Des entreprises comme Airbnb, Shopify, ou Atlassian ont transformé leur workflow en créant des design systems robustes. Dans cet article, vous apprendrez à créer votre propre système de design, de la définition des fondations jusqu’à la documentation et la maintenance.

Qu’est-ce qu’un système de design ?

Définition et composantes

Un design system complet comprend :

  • Design Tokens – Variables de design (couleurs, espacements, typographie)
  • Composants UI – Éléments d’interface réutilisables
  • Patterns – Solutions de design récurrentes
  • Guidelines – Règles d’utilisation et bonnes pratiques
  • Documentation – Comment utiliser le système
  • Code – Implémentation technique
  • Pourquoi créer un design system ?

    Avantages :

  • Cohérence : Interface unifiée à travers tous les produits
  • Efficacité : Réduction du temps de design et développement
  • Scalabilité : Facilite la croissance de l’équipe et du produit
  • Collaboration : Langage commun entre designers et développeurs
  • Qualité : Standards élevés maintenus automatiquement
  • Accessibilité : Conformité WCAG intégrée dès le départ
  • Étape 1 : Les fondations (Design Tokens)

    Les design tokens sont les valeurs atomiques de votre design : couleurs, espacements, typographie, ombres, etc.

    Structure de fichier CSS Variables

/* design-tokens.css */

:root {
  /* ============================================
     COULEURS - Palette complète
     ============================================ */

  /* Primaire - Bleu */
  --color-primary-50: #eff6ff;
  --color-primary-100: #dbeafe;
  --color-primary-200: #bfdbfe;
  --color-primary-300: #93c5fd;
  --color-primary-400: #60a5fa;
  --color-primary-500: #3b82f6;
  --color-primary-600: #2563eb;
  --color-primary-700: #1d4ed8;
  --color-primary-800: #1e40af;
  --color-primary-900: #1e3a8a;

  /* Neutrals - Gris */
  --color-neutral-50: #f9fafb;
  --color-neutral-100: #f3f4f6;
  --color-neutral-200: #e5e7eb;
  --color-neutral-300: #d1d5db;
  --color-neutral-400: #9ca3af;
  --color-neutral-500: #6b7280;
  --color-neutral-600: #4b5563;
  --color-neutral-700: #374151;
  --color-neutral-800: #1f2937;
  --color-neutral-900: #111827;

  /* Sémantiques */
  --color-success-50: #f0fdf4;
  --color-success-500: #22c55e;
  --color-success-700: #15803d;

  --color-error-50: #fef2f2;
  --color-error-500: #ef4444;
  --color-error-700: #b91c1c;

  --color-warning-50: #fffbeb;
  --color-warning-500: #f59e0b;
  --color-warning-700: #b45309;

  --color-info-50: #eff6ff;
  --color-info-500: #3b82f6;
  --color-info-700: #1d4ed8;

  /* ============================================
     COULEURS - Tokens sémantiques
     ============================================ */

  /* Texte */
  --text-primary: var(--color-neutral-900);
  --text-secondary: var(--color-neutral-700);
  --text-tertiary: var(--color-neutral-500);
  --text-disabled: var(--color-neutral-400);
  --text-inverse: #ffffff;
  --text-link: var(--color-primary-600);
  --text-link-hover: var(--color-primary-700);

  /* Arrière-plans */
  --bg-primary: #ffffff;
  --bg-secondary: var(--color-neutral-50);
  --bg-tertiary: var(--color-neutral-100);
  --bg-inverse: var(--color-neutral-900);
  --bg-overlay: rgba(0, 0, 0, 0.5);

  /* Bordures */
  --border-light: var(--color-neutral-200);
  --border-medium: var(--color-neutral-300);
  --border-strong: var(--color-neutral-400);
  --border-focus: var(--color-primary-500);

  /* ============================================
     TYPOGRAPHIE
     ============================================ */

  /* Familles de polices */
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
               'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
               sans-serif;
  --font-serif: Georgia, 'Times New Roman', Times, serif;
  --font-mono: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas,
               'Courier New', monospace;

  /* Tailles de police */
  --font-size-xs: 0.75rem;      /* 12px */
  --font-size-sm: 0.875rem;     /* 14px */
  --font-size-base: 1rem;       /* 16px */
  --font-size-lg: 1.125rem;     /* 18px */
  --font-size-xl: 1.25rem;      /* 20px */
  --font-size-2xl: 1.5rem;      /* 24px */
  --font-size-3xl: 1.875rem;    /* 30px */
  --font-size-4xl: 2.25rem;     /* 36px */
  --font-size-5xl: 3rem;        /* 48px */
  --font-size-6xl: 3.75rem;     /* 60px */

  /* Poids de police */
  --font-weight-light: 300;
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  --font-weight-extrabold: 800;

  /* Hauteurs de ligne */
  --line-height-tight: 1.25;
  --line-height-snug: 1.375;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.625;
  --line-height-loose: 2;

  /* Espacement lettres */
  --letter-spacing-tight: -0.025em;
  --letter-spacing-normal: 0;
  --letter-spacing-wide: 0.025em;
  --letter-spacing-wider: 0.05em;
  --letter-spacing-widest: 0.1em;

  /* ============================================
     ESPACEMENTS (Échelle basée sur 4px)
     ============================================ */

  --space-0: 0;
  --space-1: 0.25rem;   /* 4px */
  --space-2: 0.5rem;    /* 8px */
  --space-3: 0.75rem;   /* 12px */
  --space-4: 1rem;      /* 16px */
  --space-5: 1.25rem;   /* 20px */
  --space-6: 1.5rem;    /* 24px */
  --space-8: 2rem;      /* 32px */
  --space-10: 2.5rem;   /* 40px */
  --space-12: 3rem;     /* 48px */
  --space-16: 4rem;     /* 64px */
  --space-20: 5rem;     /* 80px */
  --space-24: 6rem;     /* 96px */
  --space-32: 8rem;     /* 128px */

  /* ============================================
     TAILLES
     ============================================ */

  /* Largeurs */
  --width-xs: 20rem;    /* 320px */
  --width-sm: 24rem;    /* 384px */
  --width-md: 28rem;    /* 448px */
  --width-lg: 32rem;    /* 512px */
  --width-xl: 36rem;    /* 576px */
  --width-2xl: 42rem;   /* 672px */
  --width-3xl: 48rem;   /* 768px */
  --width-4xl: 56rem;   /* 896px */
  --width-5xl: 64rem;   /* 1024px */
  --width-6xl: 72rem;   /* 1152px */
  --width-7xl: 80rem;   /* 1280px */

  /* Hauteurs */
  --height-screen: 100vh;
  --height-header: 4rem;
  --height-footer: 20rem;

  /* ============================================
     BORDURES
     ============================================ */

  /* Largeurs */
  --border-width-0: 0;
  --border-width-1: 1px;
  --border-width-2: 2px;
  --border-width-4: 4px;
  --border-width-8: 8px;

  /* Rayons */
  --border-radius-none: 0;
  --border-radius-sm: 0.125rem;   /* 2px */
  --border-radius-base: 0.25rem;  /* 4px */
  --border-radius-md: 0.375rem;   /* 6px */
  --border-radius-lg: 0.5rem;     /* 8px */
  --border-radius-xl: 0.75rem;    /* 12px */
  --border-radius-2xl: 1rem;      /* 16px */
  --border-radius-3xl: 1.5rem;    /* 24px */
  --border-radius-full: 9999px;

  /* ============================================
     OMBRES
     ============================================ */

  --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
  --shadow-base: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
  --shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
  --shadow-xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  --shadow-2xl: 0 30px 60px -15px rgba(0, 0, 0, 0.3);
  --shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.05);

  /* ============================================
     Z-INDEX (Layering)
     ============================================ */

  --z-index-dropdown: 1000;
  --z-index-sticky: 1020;
  --z-index-fixed: 1030;
  --z-index-modal-backdrop: 1040;
  --z-index-modal: 1050;
  --z-index-popover: 1060;
  --z-index-tooltip: 1070;

  /* ============================================
     TRANSITIONS
     ============================================ */

  --transition-fast: 150ms;
  --transition-base: 200ms;
  --transition-slow: 300ms;
  --transition-slower: 500ms;

  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);

  /* ============================================
     BREAKPOINTS (pour JavaScript)
     ============================================ */

  --breakpoint-sm: 640px;
  --breakpoint-md: 768px;
  --breakpoint-lg: 1024px;
  --breakpoint-xl: 1280px;
  --breakpoint-2xl: 1536px;
}

/* ============================================
   MODE SOMBRE
   ============================================ */

@media (prefers-color-scheme: dark) {
  :root {
    /* Inversions pour mode sombre */
    --text-primary: var(--color-neutral-50);
    --text-secondary: var(--color-neutral-300);
    --text-tertiary: var(--color-neutral-400);

    --bg-primary: var(--color-neutral-900);
    --bg-secondary: var(--color-neutral-800);
    --bg-tertiary: var(--color-neutral-700);

    --border-light: var(--color-neutral-700);
    --border-medium: var(--color-neutral-600);
    --border-strong: var(--color-neutral-500);
  }
}

/* Classe pour forcer le mode sombre */
[data-theme="dark"] {
  --text-primary: var(--color-neutral-50);
  --text-secondary: var(--color-neutral-300);
  --text-tertiary: var(--color-neutral-400);

  --bg-primary: var(--color-neutral-900);
  --bg-secondary: var(--color-neutral-800);
  --bg-tertiary: var(--color-neutral-700);

  --border-light: var(--color-neutral-700);
  --border-medium: var(--color-neutral-600);
  --border-strong: var(--color-neutral-500);
}

Étape 2 : Les composants de base

Créons une bibliothèque de composants réutilisables.

Boutons

/* components/button.css */

.btn {
  /* Base */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);

  /* Typographie */
  font-family: var(--font-sans);
  font-weight: var(--font-weight-semibold);
  text-decoration: none;
  white-space: nowrap;

  /* Bordures */
  border: var(--border-width-1) solid transparent;
  border-radius: var(--border-radius-lg);

  /* Interactivité */
  cursor: pointer;
  user-select: none;
  transition: all var(--transition-base) var(--ease-out);

  /* États désactivés */
  &:disabled,
  &[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
  }

  /* Focus visible (accessibilité) */
  &:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
  }
}

/* Variantes de taille */
.btn-sm {
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-size-sm);
  min-height: 2rem;
}

.btn-md {
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-size-base);
  min-height: 2.5rem;
}

.btn-lg {
  padding: var(--space-4) var(--space-6);
  font-size: var(--font-size-lg);
  min-height: 3rem;
}

/* Variantes de style */
.btn-primary {
  background-color: var(--color-primary-600);
  color: white;
  border-color: var(--color-primary-600);

  &:hover:not(:disabled) {
    background-color: var(--color-primary-700);
    border-color: var(--color-primary-700);
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
  }

  &:active:not(:disabled) {
    transform: translateY(0);
    box-shadow: var(--shadow-sm);
  }
}

.btn-secondary {
  background-color: transparent;
  color: var(--color-primary-600);
  border-color: var(--color-primary-600);

  &:hover:not(:disabled) {
    background-color: var(--color-primary-50);
    border-color: var(--color-primary-700);
    color: var(--color-primary-700);
  }
}

.btn-tertiary {
  background-color: transparent;
  color: var(--text-secondary);
  border-color: transparent;

  &:hover:not(:disabled) {
    background-color: var(--bg-secondary);
    color: var(--text-primary);
  }
}

.btn-danger {
  background-color: var(--color-error-600);
  color: white;
  border-color: var(--color-error-600);

  &:hover:not(:disabled) {
    background-color: var(--color-error-700);
    border-color: var(--color-error-700);
  }
}

/* Variantes de largeur */
.btn-full {
  width: 100%;
}

.btn-auto {
  width: auto;
}

/* Groupe de boutons */
.btn-group {
  display: inline-flex;
  gap: var(--space-2);
}

.btn-group-vertical {
  display: flex;
  flex-direction: column;
}
<!-- Exemples d'utilisation -->
<button class="btn btn-primary btn-md">
  Enregistrer
</button>

<button class="btn btn-secondary btn-lg">
  Annuler
</button>

<button class="btn btn-tertiary btn-sm" disabled>
  Désactivé
</button>

<a href="/action" class="btn btn-primary btn-md">
  Lien bouton
</a>

<!-- Bouton avec icône -->
<button class="btn btn-primary btn-md">
  <svg width="20" height="20" fill="currentColor">
    <path d="M10 5l7 7-7 7"></path>
  </svg>
  Suivant
</button>

<!-- Groupe de boutons -->
<div class="btn-group">
  <button class="btn btn-secondary btn-md">Précédent</button>
  <button class="btn btn-primary btn-md">Suivant</button>
</div>

Inputs (Champs de formulaire)

/* components/input.css */

.input-wrapper {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
}

.input-label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--text-primary);
}

.input-label-required::after {
  content: '*';
  color: var(--color-error-500);
  margin-left: var(--space-1);
}

.input {
  /* Base */
  width: 100%;
  padding: var(--space-3) var(--space-4);

  /* Typographie */
  font-family: var(--font-sans);
  font-size: var(--font-size-base);
  line-height: var(--line-height-normal);
  color: var(--text-primary);

  /* Bordures */
  border: var(--border-width-1) solid var(--border-medium);
  border-radius: var(--border-radius-lg);

  /* Arrière-plan */
  background-color: var(--bg-primary);

  /* Transitions */
  transition: all var(--transition-base) var(--ease-out);

  /* Placeholder */
  &::placeholder {
    color: var(--text-tertiary);
  }

  /* Focus */
  &:focus {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
  }

  /* Hover */
  &:hover:not(:focus):not(:disabled) {
    border-color: var(--border-strong);
  }

  /* Disabled */
  &:disabled {
    background-color: var(--bg-secondary);
    color: var(--text-disabled);
    cursor: not-allowed;
    opacity: 0.6;
  }

  /* Erreur */
  &[aria-invalid="true"],
  &.input-error {
    border-color: var(--color-error-500);
  }

  &[aria-invalid="true"]:focus,
  &.input-error:focus {
    box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
  }

  /* Succès */
  &.input-success {
    border-color: var(--color-success-500);
  }

  &.input-success:focus {
    box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.1);
  }
}

/* Tailles */
.input-sm {
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-size-sm);
}

.input-lg {
  padding: var(--space-4) var(--space-5);
  font-size: var(--font-size-lg);
}

/* Textarea */
.textarea {
  resize: vertical;
  min-height: 6rem;
}

/* Messages d'aide et d'erreur */
.input-hint {
  font-size: var(--font-size-sm);
  color: var(--text-tertiary);
}

.input-error-message {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-error-700);
}

.input-success-message {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-success-700);
}

/* Input avec icône */
.input-with-icon {
  position: relative;
}

.input-with-icon .input {
  padding-left: var(--space-10);
}

.input-icon {
  position: absolute;
  left: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-tertiary);
  pointer-events: none;
}
<!-- Exemples d'utilisation -->

<!-- Input basique -->
<div class="input-wrapper">
  <label for="email" class="input-label input-label-required">
    Email
  </label>
  <input
    type="email"
    id="email"
    class="input"
    placeholder="vous@exemple.com"
    required
  >
  Nous ne partagerons jamais votre email
</div>

<!-- Input avec erreur -->
<div class="input-wrapper">
  <label for="password" class="input-label">
    Mot de passe
  </label>
  <input
    type="password"
    id="password"
    class="input input-error"
    aria-invalid="true"
    aria-describedby="password-error"
  >
  
    ⚠ Le mot de passe doit contenir au moins 8 caractères
  
</div>

<!-- Input avec succès -->
<div class="input-wrapper">
  <label for="username" class="input-label">
    Nom d'utilisateur
  </label>
  <input
    type="text"
    id="username"
    class="input input-success"
    value="johndoe"
  >
  
    ✓ Ce nom d'utilisateur est disponible
  
</div>

<!-- Input avec icône -->
<div class="input-wrapper">
  <label for="search" class="input-label">
    Rechercher
  </label>
  <div class="input-with-icon">
    
      <svg width="20" height="20" fill="currentColor">
        <path d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"/>
      </svg>
    
    <input
      type="search"
      id="search"
      class="input"
      placeholder="Rechercher..."
    >
  </div>
</div>

<!-- Textarea -->
<div class="input-wrapper">
  <label for="message" class="input-label">
    Message
  </label>
  <textarea
    id="message"
    class="input textarea"
    rows="5"
    placeholder="Votre message..."
  ></textarea>
</div>

<!-- Select -->
<div class="input-wrapper">
  <label for="country" class="input-label">
    Pays
  </label>
  <select id="country" class="input">
    <option value="">Sélectionnez un pays</option>
    <option value="be">Belgique</option>
    <option value="ch">Suisse</option>
    <option value="ca">Canada</option>
  </select>
</div>

Cards (Cartes)

/* components/card.css */

.card {
  background: var(--bg-primary);
  border: var(--border-width-1) solid var(--border-light);
  border-radius: var(--border-radius-xl);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: all var(--transition-base) var(--ease-out);
}

.card:hover {
  box-shadow: var(--shadow-md);
}

/* Card interactive (cliquable) */
.card-interactive {
  cursor: pointer;
}

.card-interactive:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}

.card-interactive:active {
  transform: translateY(-2px);
}

/* Parties de la carte */
.card-header {
  padding: var(--space-6);
  border-bottom: var(--border-width-1) solid var(--border-light);
}

.card-body {
  padding: var(--space-6);
}

.card-footer {
  padding: var(--space-6);
  border-top: var(--border-width-1) solid var(--border-light);
  background: var(--bg-secondary);
}

/* Card avec image */
.card-image {
  width: 100%;
  height: auto;
  display: block;
}

.card-image-container {
  position: relative;
  aspect-ratio: 16/9;
  overflow: hidden;
  background: var(--bg-secondary);
}

.card-image-container img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Éléments de contenu */
.card-title {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
  color: var(--text-primary);
  margin-bottom: var(--space-2);
}

.card-subtitle {
  font-size: var(--font-size-sm);
  color: var(--text-tertiary);
  margin-bottom: var(--space-4);
}

.card-description {
  font-size: var(--font-size-base);
  line-height: var(--line-height-relaxed);
  color: var(--text-secondary);
}

/* Variantes */
.card-outlined {
  border-width: var(--border-width-2);
  box-shadow: none;
}

.card-elevated {
  border: none;
  box-shadow: var(--shadow-lg);
}

.card-flat {
  border: none;
  box-shadow: none;
}

/* Card avec accent coloré */
.card-accent {
  border-top: 4px solid var(--color-primary-500);
}

.card-accent-success {
  border-top-color: var(--color-success-500);
}

.card-accent-error {
  border-top-color: var(--color-error-500);
}

.card-accent-warning {
  border-top-color: var(--color-warning-500);
}

/* Grille de cartes */
.card-grid {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
<!-- Carte basique -->
<div class="card">
  <div class="card-body">
    <h3 class="card-title">Titre de la carte</h3>
    <p class="card-description">
      Description du contenu de la carte avec quelques détails supplémentaires.
    </p>
  </div>
</div>

<!-- Carte complète avec image -->
<div class="card card-interactive">
  <div class="card-image-container">
    <img src="image.jpg" alt="Description" class="card-image">
  </div>
  <div class="card-body">
    <div class="card-subtitle">Catégorie</div>
    <h3 class="card-title">Titre de l'article</h3>
    <p class="card-description">
      Un aperçu intéressant du contenu qui donne envie d'en savoir plus.
    </p>
  </div>
  <div class="card-footer">
    <a href="#" class="btn btn-primary btn-sm">Lire la suite</a>
  </div>
</div>

<!-- Carte avec accent -->
<div class="card card-accent-success">
  <div class="card-header">
    <h3 class="card-title">Compte vérifié</h3>
  </div>
  <div class="card-body">
    <p class="card-description">
      Votre compte a été vérifié avec succès. Vous pouvez maintenant accéder à toutes les fonctionnalités.
    </p>
  </div>
</div>

<!-- Grille de cartes -->
<div class="card-grid">
  <div class="card">...</div>
  <div class="card">...</div>
  <div class="card">...</div>
</div>

Étape 3 : Composants complexes

/* components/modal.css */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--bg-overlay);
  z-index: var(--z-index-modal-backdrop);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  opacity: 0;
  visibility: hidden;
  transition: all var(--transition-base) var(--ease-out);
}

.modal-overlay.is-open {
  opacity: 1;
  visibility: visible;
}

.modal {
  background: var(--bg-primary);
  border-radius: var(--border-radius-xl);
  box-shadow: var(--shadow-2xl);
  max-width: var(--width-lg);
  width: 100%;
  max-height: 90vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transform: scale(0.95);
  transition: transform var(--transition-base) var(--ease-out);
  z-index: var(--z-index-modal);
}

.modal-overlay.is-open .modal {
  transform: scale(1);
}

.modal-header {
  padding: var(--space-6);
  border-bottom: var(--border-width-1) solid var(--border-light);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.modal-title {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  color: var(--text-primary);
}

.modal-close {
  width: 2.5rem;
  height: 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--border-radius-base);
  border: none;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all var(--transition-fast) var(--ease-out);
}

.modal-close:hover {
  background: var(--bg-secondary);
  color: var(--text-primary);
}

.modal-body {
  padding: var(--space-6);
  overflow-y: auto;
  flex: 1;
}

.modal-footer {
  padding: var(--space-6);
  border-top: var(--border-width-1) solid var(--border-light);
  display: flex;
  gap: var(--space-3);
  justify-content: flex-end;
}

/* Tailles */
.modal-sm .modal {
  max-width: var(--width-sm);
}

.modal-md .modal {
  max-width: var(--width-md);
}

.modal-lg .modal {
  max-width: var(--width-lg);
}

.modal-xl .modal {
  max-width: var(--width-2xl);
}

.modal-full .modal {
  max-width: 95vw;
  max-height: 95vh;
}
<!-- Modal HTML -->
<div class="modal-overlay" id="exampleModal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
  <div class="modal">
    <div class="modal-header">
      <h2 class="modal-title" id="modal-title">Titre du modal</h2>
      <button class="modal-close" aria-label="Fermer">
        <svg width="20" height="20" fill="currentColor">
          <path d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"/>
        </svg>
      </button>
    </div>
    <div class="modal-body">
      <p>Contenu du modal...</p>
    </div>
    <div class="modal-footer">
      <button class="btn btn-tertiary btn-md">Annuler</button>
      <button class="btn btn-primary btn-md">Confirmer</button>
    </div>
  </div>
</div>
// modal.js - Gestion du modal
class Modal {
  constructor(modalId) {
    this.modal = document.getElementById(modalId);
    this.closeBtn = this.modal.querySelector('.modal-close');
    this.overlay = this.modal;

    this.init();
  }

  init() {
    // Fermer avec bouton
    this.closeBtn?.addEventListener('click', () => this.close());

    // Fermer en cliquant sur l'overlay
    this.overlay.addEventListener('click', (e) => {
      if (e.target === this.overlay) {
        this.close();
      }
    });

    // Fermer avec Escape
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && this.isOpen()) {
        this.close();
      }
    });
  }

  open() {
    this.modal.classList.add('is-open');
    document.body.style.overflow = 'hidden';

    // Focus trap
    const focusableElements = this.modal.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];

    firstElement?.focus();

    this.modal.addEventListener('keydown', (e) => {
      if (e.key === 'Tab') {
        if (e.shiftKey && document.activeElement === firstElement) {
          e.preventDefault();
          lastElement.focus();
        } else if (!e.shiftKey && document.activeElement === lastElement) {
          e.preventDefault();
          firstElement.focus();
        }
      }
    });
  }

  close() {
    this.modal.classList.remove('is-open');
    document.body.style.overflow = '';
  }

  isOpen() {
    return this.modal.classList.contains('is-open');
  }
}

// Utilisation
const myModal = new Modal('exampleModal');

// Ouvrir le modal
document.getElementById('openModal').addEventListener('click', () => {
  myModal.open();
});

Étape 4 : Documentation

Créez une page de documentation interactive.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Design System - Documentation</title>
  <link rel="stylesheet" href="design-tokens.css">
  <link rel="stylesheet" href="components.css">
  <style>
    /* Documentation spécifique */
    .doc-container {
      max-width: 1200px;
      margin: 0 auto;
      padding: var(--space-8) var(--space-6);
    }

    .doc-sidebar {
      position: sticky;
      top: var(--space-6);
      max-height: calc(100vh - var(--space-12));
      overflow-y: auto;
    }

    .doc-nav {
      list-style: none;
    }

    .doc-nav a {
      display: block;
      padding: var(--space-2) var(--space-4);
      color: var(--text-secondary);
      text-decoration: none;
      border-radius: var(--border-radius-base);
      transition: all var(--transition-fast);
    }

    .doc-nav a:hover {
      background: var(--bg-secondary);
      color: var(--text-primary);
    }

    .doc-nav a.active {
      background: var(--color-primary-50);
      color: var(--color-primary-700);
      font-weight: var(--font-weight-semibold);
    }

    .doc-section {
      margin-bottom: var(--space-16);
    }

    .doc-section-title {
      font-size: var(--font-size-3xl);
      font-weight: var(--font-weight-bold);
      margin-bottom: var(--space-6);
      padding-bottom: var(--space-4);
      border-bottom: 2px solid var(--border-light);
    }

    .doc-component-title {
      font-size: var(--font-size-2xl);
      font-weight: var(--font-weight-bold);
      margin-top: var(--space-8);
      margin-bottom: var(--space-4);
    }

    .doc-example {
      padding: var(--space-8);
      background: var(--bg-secondary);
      border-radius: var(--border-radius-lg);
      margin-bottom: var(--space-4);
    }

    .doc-code {
      background: var(--color-neutral-900);
      color: var(--color-neutral-50);
      padding: var(--space-4);
      border-radius: var(--border-radius-lg);
      overflow-x: auto;
      font-family: var(--font-mono);
      font-size: var(--font-size-sm);
      line-height: var(--line-height-relaxed);
    }

    .doc-color-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
      gap: var(--space-4);
    }

    .doc-color-swatch {
      height: 100px;
      border-radius: var(--border-radius-lg);
      display: flex;
      align-items: flex-end;
      padding: var(--space-3);
      box-shadow: var(--shadow-sm);
    }

    .doc-color-label {
      background: var(--bg-primary);
      padding: var(--space-1) var(--space-2);
      border-radius: var(--border-radius-sm);
      font-size: var(--font-size-xs);
      font-family: var(--font-mono);
    }

    .doc-grid {
      display: grid;
      grid-template-columns: 250px 1fr;
      gap: var(--space-8);
    }

    @media (max-width: 1024px) {
      .doc-grid {
        grid-template-columns: 1fr;
      }

      .doc-sidebar {
        position: static;
      }
    }
  </style>
</head>
<body>
  <div class="doc-container">
    <div class="doc-grid">
      <!-- Sidebar Navigation -->
      <aside class="doc-sidebar">
        <h2 style="font-size: var(--font-size-xl); margin-bottom: var(--space-4);">
          Design System
        </h2>
        <nav>
          <ul class="doc-nav">
- <a href="#introduction" class="active">Introduction</a>
- <a href="#colors">Couleurs</a>
- <a href="#typography">Typographie</a>
- <a href="#spacing">Espacements</a>
- <a href="#buttons">Boutons</a>
- <a href="#inputs">Inputs</a>
- <a href="#cards">Cartes</a>
- <a href="#modals">Modales</a>
        </nav>
      </aside>

      <!-- Main Content -->
      <main>
        <!-- Introduction -->
        <section id="introduction" class="doc-section">
          <h1 class="doc-section-title">Design System</h1>
          <p style="font-size: var(--font-size-lg); line-height: var(--line-height-relaxed); color: var(--text-secondary);">
            Bienvenue dans notre design system. Cette bibliothèque contient tous les composants,
            styles et guidelines pour créer des interfaces cohérentes et accessibles.
          </p>
        </section>

        <!-- Couleurs -->
        <section id="colors" class="doc-section">
          <h2 class="doc-section-title">Couleurs</h2>

          <h3 class="doc-component-title">Couleur primaire</h3>
          <div class="doc-color-grid">
            <div class="doc-color-swatch" style="background: var(--color-primary-100);">
              primary-100
            </div>
            <div class="doc-color-swatch" style="background: var(--color-primary-300);">
              primary-300
            </div>
            <div class="doc-color-swatch" style="background: var(--color-primary-500);">
              primary-500
            </div>
            <div class="doc-color-swatch" style="background: var(--color-primary-700);">
              primary-700
            </div>
            <div class="doc-color-swatch" style="background: var(--color-primary-900);">
              primary-900
            </div>
          </div>
        </section>

        <!-- Boutons -->
        <section id="buttons" class="doc-section">
          <h2 class="doc-section-title">Boutons</h2>

          <h3 class="doc-component-title">Variantes</h3>
          <div class="doc-example">
            <div style="display: flex; gap: var(--space-3); flex-wrap: wrap;">
              <button class="btn btn-primary btn-md">Primary</button>
              <button class="btn btn-secondary btn-md">Secondary</button>
              <button class="btn btn-tertiary btn-md">Tertiary</button>
              <button class="btn btn-danger btn-md">Danger</button>
            </div>
          </div>

          <div class="doc-code"><button class="btn btn-primary btn-md">Primary</button>
<button class="btn btn-secondary btn-md">Secondary</button>
<button class="btn btn-tertiary btn-md">Tertiary</button>
<button class="btn btn-danger btn-md">Danger</button></div>

          <h3 class="doc-component-title">Tailles</h3>
          <div class="doc-example">
            <div style="display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap;">
              <button class="btn btn-primary btn-sm">Small</button>
              <button class="btn btn-primary btn-md">Medium</button>
              <button class="btn btn-primary btn-lg">Large</button>
            </div>
          </div>
        </section>

        <!-- Inputs -->
        <section id="inputs" class="doc-section">
          <h2 class="doc-section-title">Inputs</h2>

          <div class="doc-example">
            <div style="max-width: 400px;">
              <div class="input-wrapper">
                <label for="demo-email" class="input-label input-label-required">
                  Email
                </label>
                <input
                  type="email"
                  id="demo-email"
                  class="input"
                  placeholder="vous@exemple.com"
                >
                Nous ne partagerons jamais votre email
              </div>
            </div>
          </div>
        </section>

        <!-- Cards -->
        <section id="cards" class="doc-section">
          <h2 class="doc-section-title">Cartes</h2>

          <div class="doc-example">
            <div class="card-grid">
              <div class="card">
                <div class="card-body">
                  <h3 class="card-title">Carte basique</h3>
                  <p class="card-description">
                    Une carte simple avec titre et description.
                  </p>
                </div>
              </div>

              <div class="card card-accent">
                <div class="card-body">
                  <h3 class="card-title">Carte avec accent</h3>
                  <p class="card-description">
                    Une carte avec une bordure supérieure colorée.
                  </p>
                </div>
              </div>

              <div class="card card-elevated">
                <div class="card-body">
                  <h3 class="card-title">Carte élevée</h3>
                  <p class="card-description">
                    Une carte avec une ombre plus prononcée.
                  </p>
                </div>
              </div>
            </div>
          </div>
        </section>
      </main>
    </div>
  </div>
</body>
</html>

Étape 5 : Maintenance et évolution

Versionning

{
  "name": "my-design-system",
  "version": "1.2.0",
  "description": "Design system de MonEntreprise",
  "changelog": {
    "1.2.0": {
      "date": "2024-12-18",
      "changes": [
        "Ajout du composant Modal",
        "Amélioration de l'accessibilité des boutons",
        "Nouveaux tokens de couleur pour le mode sombre"
      ]
    },
    "1.1.0": {
      "date": "2024-11-15",
      "changes": [
        "Ajout du composant Card",
        "Refonte des inputs",
        "Corrections de bugs"
      ]
    }
  }
}

Processus de mise à jour

  • Proposer un changement : Créer une issue ou RFC
  • Design review : Validation par l’équipe design
  • Développement : Implémentation et tests
  • Documentation : Mise à jour de la doc
  • Communication : Annoncer les changements
  • Déploiement : Version publiée
  • Outils recommandés

  • Figma – Design et prototypage
  • Storybook – Documentation interactive des composants
  • Chromatic – Tests visuels automatisés
  • GitHub – Versioning et collaboration
  • npm – Distribution du package
  • Exemples du monde réel

    Material Design (Google)

  • Design system le plus utilisé au monde
  • Composants pour web, Android, iOS
  • Documentation exhaustive
  • material.io
  • Polaris (Shopify)

  • Spécialisé e-commerce
  • Tokens accessibles
  • Guidelines UX détaillées
  • polaris.shopify.com
  • Carbon (IBM)

  • Enterprise-grade
  • Forte emphase sur l’accessibilité
  • Multiples frameworks supportés
  • carbondesignsystem.com
  • Ant Design (Alibaba)

  • Populaire en Asie
  • Riche en composants complexes
  • ant.design
  • Checklist de lancement

    Avant de déployer votre design system :

    Fondations :

  • [ ] Design tokens définis (couleurs, typo, espacements)
  • [ ] Variables CSS organisées et documentées
  • [ ] Mode sombre prévu
  • [ ] Accessibilité validée (WCAG AA minimum)
  • Composants :

  • [ ] Au moins 10 composants de base
  • [ ] États interactifs définis (hover, focus, disabled)
  • [ ] Responsive sur tous breakpoints
  • [ ] Tests visuels sur navigateurs principaux
  • Documentation :

  • [ ] Page de présentation
  • [ ] Guide de démarrage
  • [ ] Documentation de chaque composant
  • [ ] Exemples de code fonctionnels
  • [ ] Guidelines d’utilisation
  • Technique :

  • [ ] Code organisé et modulaire
  • [ ] Performance optimisée
  • [ ] Compatible navigateurs modernes
  • [ ] Versionning en place
  • Équipe :

  • [ ] Formation des utilisateurs
  • [ ] Process de contribution défini
  • [ ] Support et maintenance planifiés
  • Conclusion

    Créer un design system est un investissement qui paie rapidement en termes de cohérence, d’efficacité et de qualité. Commencez petit avec les composants essentiels, documentez au fur et à mesure, et faites évoluer votre système en fonction des besoins réels de vos utilisateurs.

    Points clés à retenir :

  • Commencez par les design tokens (fondations)
  • Créez les composants de base avant les complexes
  • Documentez tout dès le début
  • Testez l’accessibilité systématiquement
  • Faites évoluer progressivement
  • Impliquez designers et développeurs
  • Prochaines étapes :

  • Créez votre fichier de design tokens
  • Implémentez 5 composants de base
  • Créez une page de documentation
  • Testez avec un projet pilote
  • Itérez en fonction des retours
  • Un design system vivant et bien maintenu devient l’atout le plus précieux d’une équipe produit !


    Ressources complémentaires :

  • Design Systems Repo – Collection de design systems
  • Storybook – Documentation interactive
  • Style Dictionary – Gestion des tokens
  • Figma – Design et prototypage
  • À propos de l'auteur

    Pascal

    Pascal — SRE & Observability Engineer. J'applique les pratiques SRE (SLI/SLO, alerting, observabilité) à mon homelab Proxmox, avec les mêmes exigences qu'en production.

    Une remarque, un retour ?

    Cet article est vivant - corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisis celui qui te convient.

    Laisser un commentaire