Cours JavaScript et React · Module 14: Styles avancés

Design Tokens et CSS systématique - Code de design cosmique

6 min de lecture
Dans cette leçon10

Pilote, imagine que chaque vaisseau de la flotte galactique utilise exactement les mêmes couleurs de statut, les mêmes espacements entre les panneaux et la même typographie sur les moniteurs. Ce n'est pas un hasard -- c'est un design system. Dans le monde du frontend, l'équivalent ce sont les design tokens -- une source de vérité centrale pour toutes les valeurs visuelles de l'application.

Que sont les Design Tokens ?

Les design tokens sont des valeurs nommées représentant des décisions de design : couleurs, espacements, tailles de police, ombres, rayons de bordure. Au lieu de disperser des nombres magiques dans tout le code, tu les définis à un seul endroit :

1// tokens.js -- source de vérité centrale
2const tokens = {
3  colors: {
4    primary: '#7c4dff',
5    primaryLight: '#b388ff',
6    primaryDark: '#651fff',
7    success: '#00e676',
8    warning: '#ffa726',
9    danger: '#f44336',
10    bgDark: '#0a0a2e',
11    bgSurface: '#1a1a4e',
12    textPrimary: '#e0e0e0',
13    textMuted: '#9e9e9e',
14    border: '#3949ab',
15  },
16  spacing: {
17    xs: '4px',
18    sm: '8px',
19    md: '16px',
20    lg: '24px',
21    xl: '32px',
22    xxl: '48px',
23  },
24  fontSize: {
25    xs: '12px',
26    sm: '14px',
27    md: '16px',
28    lg: '20px',
29    xl: '28px',
30    xxl: '36px',
31  },
32  borderRadius: {
33    sm: '4px',
34    md: '8px',
35    lg: '12px',
36    xl: '20px',
37    full: '9999px',
38  },
39  shadows: {
40    sm: '0 2px 4px rgba(0,0,0,0.1)',
41    md: '0 4px 12px rgba(0,0,0,0.2)',
42    lg: '0 8px 24px rgba(0,0,0,0.3)',
43    glow: '0 0 20px rgba(124,77,255,0.3)',
44  },
45};

Utiliser les tokens dans les composants

Une fois tes tokens définis, tu les utilises à la place des valeurs directes :

1import { tokens } from './tokens';
2
3// Dans les styles inline
4function Card({ children }) {
5  return (
6    <div style={{
7      background: tokens.colors.bgSurface,
8      padding: tokens.spacing.lg,
9      borderRadius: tokens.borderRadius.lg,
10      border: `1px solid ${tokens.colors.border}`,
11    }}>
12      {children}
13    </div>
14  );
15}
1// Dans styled-components
2const Card = styled.div`
3  background: ${tokens.colors.bgSurface};
4  padding: ${tokens.spacing.lg};
5  border-radius: ${tokens.borderRadius.lg};
6  border: 1px solid ${tokens.colors.border};
7`;

Les tokens comme variables CSS

La meilleure approche consiste à combiner les tokens JavaScript avec les variables CSS -- tu définis les tokens en JS (faciles à gérer) et tu les passes au CSS sous forme de variables (rendu efficace) :

1function applyTokens(tokens) {
2  const vars = {};
3  vars['--color-primary'] = tokens.colors.primary;
4  vars['--color-bg'] = tokens.colors.bgDark;
5  vars['--color-surface'] = tokens.colors.bgSurface;
6  vars['--color-text'] = tokens.colors.textPrimary;
7  vars['--spacing-md'] = tokens.spacing.md;
8  vars['--spacing-lg'] = tokens.spacing.lg;
9  vars['--radius-lg'] = tokens.borderRadius.lg;
10  return vars;
11}
12
13function App() {
14  return (
15    <div style={applyTokens(tokens)} className="app">
16      {/* Le CSS utilise var(--color-primary) etc. */}
17    </div>
18  );
19}

Un système de couleurs cohérent

Un bon système de couleurs a une structure :

1const colorSystem = {
2  // Couleurs de base
3  blue: { 50: '#e3f2fd', 100: '#bbdefb', 500: '#2196f3', 900: '#0d47a1' },
4  purple: { 50: '#f3e5f5', 100: '#e1bee7', 500: '#9c27b0', 900: '#4a148c' },
5
6  // Couleurs sémantiques (utilisées dans le code)
7  primary: 'var(--blue-500)',
8  success: 'var(--green-500)',
9  warning: 'var(--orange-500)',
10  danger: 'var(--red-500)',
11
12  // Couleurs de surface
13  background: '#0a0a2e',
14  surface: '#1a1a4e',
15  surfaceHover: '#2a2a5e',
16};

Les couleurs sémantiques (primary, success, danger) sont séparées des couleurs de base (blue-500, red-400). De cette façon, changer le thème n'est qu'un changement de mapping, et non une recherche dans tout le code.

Système d'espacement

Un espacement basé sur une échelle -- chaque valeur suivante est un multiple de la base :

1// Échelle de 4px (à la Tailwind)
2const spacing = {
3  0: '0px',
4  1: '4px',
5  2: '8px',
6  3: '12px',
7  4: '16px',
8  5: '20px',
9  6: '24px',
10  8: '32px',
11  10: '40px',
12  12: '48px',
13  16: '64px',
14};

Utiliser une échelle plutôt que des valeurs arbitraires garantit la cohérence visuelle -- les éléments à l'écran sont espacés uniformément, ce qui donne un aspect professionnel.

La typographie comme système

1const typography = {
2  fontFamily: {
3    sans: "'Inter', 'Segoe UI', sans-serif",
4    mono: "'Fira Code', monospace",
5  },
6  fontSize: {
7    xs: ['12px', { lineHeight: '16px' }],
8    sm: ['14px', { lineHeight: '20px' }],
9    base: ['16px', { lineHeight: '24px' }],
10    lg: ['20px', { lineHeight: '28px' }],
11    xl: ['28px', { lineHeight: '36px' }],
12    '2xl': ['36px', { lineHeight: '44px' }],
13  },
14  fontWeight: {
15    normal: 400,
16    medium: 500,
17    semibold: 600,
18    bold: 700,
19  },
20};

Chaque taille de police a une hauteur de ligne attribuée -- ce qui élimine le chaos typographique.

Token Composition -- composer les tokens

Un pattern avancé est la token composition -- créer des tokens plus spécifiques à partir des tokens de base :

1// Tokens de base (primitifs)
2const primitives = {
3  blue500: '#7c4dff',
4  blue300: '#b388ff',
5  green500: '#00e676',
6  red500: '#f44336',
7  gray900: '#0a0a2e',
8  gray800: '#1a1a4e',
9  gray100: '#e0e0e0',
10  gray400: '#9e9e9e',
11};
12
13// Tokens sémantiques (composés à partir des tokens de base)
14const semantic = {
15  colorPrimary: primitives.blue500,
16  colorPrimaryLight: primitives.blue300,
17  colorSuccess: primitives.green500,
18  colorDanger: primitives.red500,
19  colorBackground: primitives.gray900,
20  colorSurface: primitives.gray800,
21  colorText: primitives.gray100,
22  colorTextMuted: primitives.gray400,
23};
24
25// Tokens de composant (composés à partir des tokens sémantiques)
26const components = {
27  buttonPrimaryBg: semantic.colorPrimary,
28  buttonPrimaryText: '#ffffff',
29  buttonDangerBg: semantic.colorDanger,
30  cardBg: semantic.colorSurface,
31  cardBorder: primitives.blue500 + '40',
32};

Trois couches : base (valeurs) -> sémantique (sens) -> composant (usage). Changer un seul token de base répercute les mises à jour partout.

Style Dictionary -- automatiser les tokens

Dans les grands projets, les tokens sont stockés au format JSON et générés automatiquement pour différentes plateformes à l'aide de l'outil Style Dictionary :

1{
2  "color": {
3    "primary": { "value": "#7c4dff" },
4    "success": { "value": "#00e676" },
5    "background": { "value": "#0a0a2e" }
6  },
7  "spacing": {
8    "sm": { "value": "8px" },
9    "md": { "value": "16px" },
10    "lg": { "value": "24px" }
11  }
12}

À partir de cela, Style Dictionary génère :

  • Variables CSS -- pour les applications web
  • JavaScript/TypeScript -- pour React
  • Swift/Kotlin -- pour les applications mobiles

Une seule source de vérité, plusieurs formats de sortie.

Automatiser la cohérence

Créer un composant TokenPreview aide à contrôler tout le système :

1function TokenPreview({ tokens }) {
2  return (
3    <div>
4      <h2>Colors</h2>
5      {Object.entries(tokens.colors).map(([name, value]) => (
6        <div key={name} style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
7          <div style={{ width: 40, height: 40, background: value, borderRadius: 8 }} />
8          <span>{name}: {value}</span>
9        </div>
10      ))}
11
12      <h2>Spacing</h2>
13      {Object.entries(tokens.spacing).map(([name, value]) => (
14        <div key={name} style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
15          <div style={{ width: value, height: 16, background: '#7c4dff' }} />
16          <span>{name}: {value}</span>
17        </div>
18      ))}
19    </div>
20  );
21}

Quand utiliser les design tokens ?

  • Tout projet plus grand qu'une landing page -- les tokens font gagner du temps sur le long terme
  • Collaboration en équipe -- chaque développeur utilise les mêmes valeurs
  • Plusieurs thèmes -- changer un seul objet change toute l'apparence
  • Cohérence multiplateforme -- Style Dictionary génère des tokens pour React, React Native, iOS, Android
  • Token composition -- trois couches (base -> sémantique -> composant) donnent un contrôle total
Code de cette leçon : App.jsx
1import React, { useState } from 'react';
2import './styles.css';
3
4// Design tokens -- source de vérité centrale
5const tokens = {
6  colors: {
7    primary: '#7c4dff', primaryLight: '#b388ff', primaryDark: '#651fff',
8    success: '#00e676', warning: '#ffa726', danger: '#f44336',
9    bgDark: '#0a0a2e', bgSurface: '#1a1a4e',
10    textPrimary: '#e0e0e0', textMuted: '#9e9e9e', border: '#3949ab',
11  },
12  spacing: { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px' },
13  fontSize: { xs: '12px', sm: '14px', md: '16px', lg: '20px', xl: '28px' },
14  borderRadius: { sm: '4px', md: '8px', lg: '12px', full: '9999px' },
15};
16
17// Conversion des tokens en variables CSS
18function tokensToVars(t) {
19  return {
20    '--color-primary': t.colors.primary,
21    '--color-primary-light': t.colors.primaryLight,
22    '--color-success': t.colors.success,
23    '--color-warning': t.colors.warning,
24    '--color-danger': t.colors.danger,
25    '--color-bg': t.colors.bgDark,
26    '--color-surface': t.colors.bgSurface,
27    '--color-text': t.colors.textPrimary,
28    '--color-text-muted': t.colors.textMuted,
29    '--color-border': t.colors.border,
30    '--spacing-sm': t.spacing.sm,
31    '--spacing-md': t.spacing.md,
32    '--spacing-lg': t.spacing.lg,
33    '--radius-lg': t.borderRadius.lg,
34  };
35}
36
37// Aperçu des couleurs
38function ColorSwatch({ name, value }) {
39  return (
40    <div className="swatch">
41      <div className="swatch-color" style={{ background: value }} />
42      <div className="swatch-info">
43        <span className="swatch-name">{name}</span>
44        <span className="swatch-value">{value}</span>
45      </div>
46    </div>
47  );
48}
49
50// Aperçu des espacements
51function SpacingBar({ name, value }) {
52  return (
53    <div className="spacing-row">
54      <span className="spacing-name">{name}</span>
55      <div className="spacing-bar" style={{ width: value, background: 'var(--color-primary)' }} />
56      <span className="spacing-value">{value}</span>
57    </div>
58  );
59}
60
61// Exemple d'utilisation des tokens dans un composant
62function TokenCard({ title, children }) {
63  return (
64    <div className="token-card">
65      <h3 className="token-card-title">{title}</h3>
66      {children}
67    </div>
68  );
69}
70
71function TokenButton({ variant, children }) {
72  return (
73    <button className={`token-btn token-btn-${variant}`}>{children}</button>
74  );
75}
76
77export default function App() {
78  return (
79    <div style={tokensToVars(tokens)} className="app">
80      <h1 className="heading">Design Tokens</h1>
81      <p className="subtitle">Source de vérité centrale -- toutes les valeurs à un seul endroit</p>
82
83      <TokenCard title="Couleurs">
84        <div className="swatch-grid">
85          {Object.entries(tokens.colors).map(([name, value]) => (
86            <ColorSwatch key={name} name={name} value={value} />
87          ))}
88        </div>
89      </TokenCard>
90
91      <TokenCard title="Espacements">
92        {Object.entries(tokens.spacing).map(([name, value]) => (
93          <SpacingBar key={name} name={name} value={value} />
94        ))}
95      </TokenCard>
96
97      <TokenCard title="Composants utilisant les tokens">
98        <div className="component-demo">
99          <TokenButton variant="primary">Primary</TokenButton>
100          <TokenButton variant="success">Success</TokenButton>
101          <TokenButton variant="danger">Danger</TokenButton>
102          <TokenButton variant="outline">Outline</TokenButton>
103        </div>
104        <p className="demo-note">
105          Chaque bouton utilise var(--color-...) des tokens.
106          Changer un token change tous les composants d'un coup !
107        </p>
108      </TokenCard>
109    </div>
110  );
111}

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Que sont les design tokens dans le contexte du développement frontend ?

  2. 2. Pourquoi vaut-il la peine de séparer les couleurs sémantiques (primary, danger) des couleurs de base (blue-500, red-400) dans un système de design tokens ?

Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Tri horizontal

    Ordonne la syntaxe pour utiliser un token de couleur en CSS avec une variable CSS :

  • Tri horizontal

    Ordonne la syntaxe pour définir un token d'espacement dans un objet JavaScript :

  • Clique dans l'ordre

    Ordonne les étapes pour convertir un token JavaScript en variable CSS dans React :

  • Tri vertical

    Ordonne les couches du système de design tokens de la plus basique à la plus spécifique :

  • Tri vertical

    Ordonne les étapes pour implémenter les design tokens dans un nouveau projet React :

  • Éditeur de code

    Crée un système de design tokens et utilise-le dans un composant

  • Tri vertical

    Ordonne les tokens de taille de police du plus petit au plus grand :

  • Éditeur de code

    Crée un aperçu visuel des tokens de couleur

Articles utiles