Système de design
Le système est l'artefact. Tout le reste en découle. Un livrable qui invente ses propres couleurs et espacements est une pièce unique ; un livrable construit sur des tokens peut être étendu, rebrandé et maintenu cohérent.
Livrables
Trois fichiers, et la séparation est l'essentiel :
design/tokens.css— les tokens, et rien d'autre. Des propriétés CSS personnalisées simples.design/components.css— les composants, sous forme de classes construites uniquement à partir des tokens sémantiques.design/Design System.html— le spécimen. Il<link>les deux fichiers et les documente. Documentation seulement : il ne déclare aucune valeur propre.
Les deux fichiers CSS sont le système : ce sont eux que les autres applications copient. Le spécimen reste dans le projet comme leur documentation.
Sections du spécimen, dans cet ordre :
- Intention — trois phrases : à qui cela s'adresse, ce que cela doit évoquer, ce que cela n'est explicitement pas.
- Couleur — chaque échantillon avec son nom de token, la valeur résolue et les appariements qui passent le contraste.
- Typographie — familles, graisses, l'échelle avec un rôle nommé pour chaque palier, et un échantillon de paragraphe à la taille du corps.
- Espace et forme — l'échelle d'espacement, les rayons, les ombres, les bordures.
- Composants — chacun rendu dans ses états réels, à côté du balisage qu'une application copie.
- Règles — les cinq ou six décisions que quelqu'un prendrait mal.
- Utilisation dans une application — la version, les deux fichiers à copier, l'ordre de chargement, la balise
<link>de la police, la classe racine, et ce qui a changé depuis la dernière version.
Tokens
Deux couches. Les primitives contiennent les valeurs brutes ; les tokens sémantiques nomment un rôle et pointent vers une primitive. Les composants ne référencent que les tokens sémantiques — c'est ce qui permet un rebranding en une seule ligne.
Écrivez ceci dans design/tokens.css. C'est le seul fichier du projet où une valeur brute peut apparaître. acme représente le préfixe du système (voir Partage) :
/* Acme design system 1.0.0 — tokens. Copiez ce fichier tel quel ; modifiez-le dans son projet source. */
:root {
/* primitives */
--acme-ink-900: oklch(0.22 0.02 260);
--acme-ink-600: oklch(0.48 0.02 260);
--acme-paper-50: oklch(0.99 0.005 90);
--acme-paper-100: oklch(0.96 0.008 90);
--acme-line-200: oklch(0.90 0.006 90);
--acme-accent-500: oklch(0.55 0.14 35);
--acme-accent-600: oklch(0.45 0.15 35);
--acme-accent-100: oklch(0.94 0.03 35);
/* sémantique */
--acme-text: var(--acme-ink-900);
--acme-text-muted: var(--acme-ink-600);
--acme-bg: var(--acme-paper-50);
--acme-surface: var(--acme-paper-100);
--acme-border: var(--acme-line-200);
--acme-accent: var(--acme-accent-500);
--acme-accent-hover: var(--acme-accent-600);
--acme-accent-wash: var(--acme-accent-100);
/* typographie */
--acme-font-display: "Fraunces", Georgia, serif;
--acme-font-text: "Public Sans", system-ui, sans-serif;
--acme-step--1: 0.875rem;
--acme-step-0: 1rem;
--acme-step-1: 1.25rem;
--acme-step-2: 1.5625rem;
--acme-step-3: 1.953rem;
--acme-step-4: 2.441rem;
--acme-step-5: 3.052rem;
/* espace, sur une base de 4px */
--acme-s-1: 4px; --acme-s-2: 8px; --acme-s-3: 12px; --acme-s-4: 16px;
--acme-s-5: 24px; --acme-s-6: 32px; --acme-s-7: 48px; --acme-s-8: 64px; --acme-s-9: 96px;
/* forme et mouvement */
--acme-radius-sm: 4px; --acme-radius: 8px; --acme-radius-lg: 16px;
--acme-border-width: 1px; --acme-ring-width: 2px;
--acme-shadow-1: 0 1px 2px oklch(0.2 0.02 260 / 0.06);
--acme-shadow-2: 0 4px 16px oklch(0.2 0.02 260 / 0.08);
--acme-ease: cubic-bezier(0.2, 0.7, 0.3, 1);
--acme-dur: 160ms;
}
Ce bloc est un point de départ, pas un modèle à copier aveuglément. Modifiez la teinte d'accentuation, la paire typographique et la chaleur de l'encre pour correspondre au brief — mais gardez la structure.
Règles pour les tokens
- Le contraste est mesuré, pas estimé visuellement. Texte du corps 4,5:1 par rapport à son fond réel ; grands textes et bordures UI 3:1. Chaque page d'échantillons indique ses appariements.
- Oklch, pas de hex aléatoire. Des palettes avec des paliers de luminosité égaux évitent un rendu boueux. Gardez la chroma approximativement constante dans une famille de teintes.
- Pas de tokens inutilisés. Si rien ne référence un token, supprimez-le.
- Aucune valeur en dehors de ce fichier. Un hex, une valeur littérale
oklch(), une taille en px ou une ombre ailleurs est un token qui n'a pas encore été nommé. - Deux familles typographiques. Une pour l'affichage, une pour le texte. Les piles de secours doivent être réelles, pas seulement
sans-serif. - Échelle, pas d'improvisation. Si une valeur n'est pas sur l'échelle d'espacement ou de typographie, ajoutez-la délibérément ou utilisez la plus proche.
- Mode sombre seulement si demandé — mais gardez les couleurs sémantiques pour que l'ajout ultérieur soit un échange de token, pas une réécriture.
Composants
Écrivez-les dans design/components.css, et représentez chacun dans le spécimen dans les états par défaut, survol, focus, actif et désactivé quand ils s'appliquent :
- Bouton — primaire, secondaire, fantôme. Une seule taille suffit généralement.
- Champ de texte et sélection, avec étiquette, texte d'aide et état d'erreur.
- Carte, avec un exemple de contenu réel, pas « Lorem ipsum ».
- Navigation, en version bureau et rétrécie.
- Tableau ou ligne de liste avec un en-tête.
- Badge et étiquette.
- Modale ou dialogue.
- État vide.
- Toast ou notification en ligne.
Chaque composant utilise uniquement des tokens sémantiques. Si un composant a besoin d'une valeur qui n'a pas de token, c'est une lacune dans le système — ajoutez le token, ne codez pas la valeur en dur.
/* Acme design system 1.0.0 — components. Needs tokens.css from the same version, loaded first. */
.acme {
font-family: var(--acme-font-text);
font-size: var(--acme-step-0);
color: var(--acme-text);
background: var(--acme-bg);
}
.acme-button {
font: inherit;
padding: var(--acme-s-2) var(--acme-s-4);
border: var(--acme-border-width) solid transparent;
border-radius: var(--acme-radius);
background: var(--acme-accent);
color: var(--acme-bg);
transition: background var(--acme-dur) var(--acme-ease);
}
.acme-button:hover { background: var(--acme-accent-hover); }
.acme-button:focus-visible { outline: var(--acme-ring-width) solid var(--acme-accent); outline-offset: var(--acme-ring-width); }
.acme-button--ghost { background: transparent; color: var(--acme-accent); border-color: var(--acme-border); }
Partage
Les autres applications prennent le système en copiant tokens.css et components.css. Les règles ci-dessous permettent à une copie de fonctionner n'importe où.
- Préfixez tout. Choisissez un nom court pour le système avant le premier token et mettez-le sur chaque propriété personnalisée et chaque classe :
--acme-accent,.acme-button. Les copies se placent ensuite à côté du CSS de l'application sans collisions. Le préfixe fait partie de l'API ; ne le changez jamais. - CSS pur dans les deux fichiers. Pas de directives Tailwind, pas de syntaxe de préprocesseur, pas d'imbrication, pas d'exigence
@layer, pas de@importentre eux. Une application React, une page statique ou un autre outil de design doit obtenir des résultats identiques à partir d'une copie directe. - Tout est résolu à l'exécution. Pas de calcul de couleur au moment de la construction, pas de sortie compilée.
- Rien de global dans
components.css. Pas de réinitialisations, pas de sélecteurs d'élément, rien surhtmloubody: déposer le fichier dans une application ne doit pas la restyler. Les styles de texte de base vivent sur la classe racine.acme, qu'une application ajoute là où elle les souhaite. - Les états proviennent du balisage. Stylisez
:hover,:focus-visible,:disabled,[aria-invalid="true"],[aria-current]et[aria-expanded]plutôt que des classes d'état qu'une application devrait basculer, afin que les composants fonctionnent avec n'importe quel HTML frameworké. - Facile à surcharger. Une classe par règle si possible, pas d'ID, pas de
!important. - Une version pour les deux fichiers. La première ligne de chacun nomme le système et sa version ; incrémentez-la à chaque changement, dans les deux. Correctif pour une valeur modifiée, mineure pour un nouveau token ou composant, majeure pour une suppression. Renommez en ajoutant le nouveau token et en conservant l'ancien comme alias,
--acme-old: var(--acme-new);, jusqu'à ce que les applications aient migré ; la suppression de l'alias est la version majeure. - Les choses spécifiques au projet restent dans le spécimen. Les balises
<link>de polices, les largeurs de conteneur et les chemins d'actifs ne sont pas des tokens. Le spécimen montre la balise<link>de la police pour qu'une application puisse la copier avec les fichiers.
Si le projet cible a déjà un fichier de tokens, adaptez-vous à sa dénomination avant d'inventer la vôtre. Un vocabulaire partagé vaut mieux qu'un meilleur nom.
Page de spécimen
Le spécimen est une documentation pour les humains, pas une galerie de composants à admirer. Montrez le nom du token à côté de l'échantillon. Montrez chaque composant avec le balisage qu'une application copie. Notez les états difficiles à voir (anneaux de focus, désactivé).
Liez les deux fichiers, d'abord les tokens, et mettez la classe racine sur <body>. Utilisez la propre typographie et l'espacement du système pour disposer le spécimen — c'est la première preuve que le système fonctionne.
Lisez les valeurs du fichier au lieu de les taper, afin que la documentation ne puisse pas dériver :
<link rel="stylesheet" href="tokens.css">
<link rel="stylesheet" href="components.css">
…
<body class="acme">
<figure class="swatch" data-token="--acme-accent-500"></figure>
…
<script>
for (const el of document.querySelectorAll('[data-token]')) {
const name = el.dataset.token;
const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
el.dataset.value = value; // affiché par CSS
el.style.setProperty('--swatch', value); // peint par CSS
}
</script>
L'échantillon se peint lui-même à partir du token et affiche la valeur résolue par le navigateur. Un hex tapé à la main est un futur mensonge.
Finalisation
Exécutez design-verify sur le spécimen. Il liste toute occurrence de couleur littérale dans le spécimen ou dans components.css ; il ne devrait y en avoir aucune. Vérifiez que chaque étiquette d'échantillon est lisible, que chaque composant est aligné sur la même grille de base, et qu'aucun texte échantillon ne déborde de son conteneur à 1024 px de large.
Lorsque le système va dans une autre application, celle-ci copie tokens.css et components.css inchangés, ainsi que la balise <link> de la police depuis le spécimen — pas le spécimen lui-même. Exportez une copie autonome du spécimen uniquement si elle doit être lue dans un endroit sans le dossier.