ADR 025
StatutStatus ActifActive
DateDate 2026-05-29
DécideursDecision makers Guilherme Negreiros — Design System Lead

ADR-025 — Densité d'espacement : floor/ceil sur grille 4px via math tokens

Date : 2026-05-29 Statut : ✅ Actif — v2 (correction du 2026-05-29 : technique floor/ceil) Décideurs : Guilherme Negreiros — Design System Lead Type: contract Chemin logique: decisions/ADR-025-densite-espacement-math-tokens.md Lecture avant: AGENTS.md, DESIGN.md, .claude/rules/tokens-system.md Relations: tokens/primitives.json, tokens/semantic.json, ADR-020-grille-4px.md, ADR-011-tokens-studio.md


Contexte

Un même composant doit s'adapter à des contextes d'usage radicalement différents :

Les tokens d'espacement précédents avaient des valeurs fixes. Les équipes contournaient ce système en créant des valeurs locales, produisant de la dérive.

Correction v2 — problème de la v1

La v1 utilisait un facteur unique swappable : {primitive.space.4} * {semantic.space.density.factor}. Ce pattern a un défaut structurel : avec un seul token de facteur, il est impossible d'encoder la direction d'arrondi (floor pour compact, ceil pour comfortable). Certains résultats atterrissaient hors de la grille 4px :

TokenCompact (×0.75)Comfortable (×1.25)
space.2 (8px)6px ✗ hors grille10px ✗ hors grille
space.5 (20px)15px ✗ hors grille25px ✗ hors grille

Source de la correction : _Sam's fancy math equations in Tokens Studio_ (Sami Am Designs, 2024) — section « Scales that round to a (4)pixel grid ».


Décision

Trois niveaux de densité

ModeFacteurArrondiContexte type
compact0.75floor()Dashboards, tableaux, outils data-dense
normal1.0aucunUsage courant — formulaires, settings, profil
comfortable1.25ceil()Marketing, onboarding, pages de lecture

Technique : floor/ceil + grid-unit

Formule générale :

compact    → floor(valeur × facteur / grid-unit) × grid-unit
comfortable → ceil(valeur × facteur / grid-unit) × grid-unit

Avec primitive.density.grid-unit = 4.

Cette technique, issue de _Sam's math equations_, garantit que 100% des valeurs calculées sont des multiples de 4px, quelle que soit la valeur de base.

Implémentation dans les tokens

Niveau primitif :

"primitive.density.factor.normal"      = 1
"primitive.density.factor.comfortable" = 1.25
"primitive.density.factor.compact"     = 0.75
"primitive.density.grid-unit"          = 4

Niveau sémantique — trois groupes explicites :

"semantic.space.control.padding-x"            = {primitive.space.4}            // 16px (normal)
"semantic.space.compact.control.padding-x"    = floor(space.4 × 0.75 / 4) × 4  // 12px
"semantic.space.comfortable.control.padding-x"= ceil(space.4 × 1.25 / 4) × 4   // 20px

Syntaxe Tokens Studio réelle :

"value": "floor({primitive.space.4} * {semantic.space.density.factor.compact} / {primitive.density.grid-unit}) * {primitive.density.grid-unit}"

Résultats par mode — toutes valeurs sur grille 4px

Token sémantiqueBaseNormalCompactComfortable
*.control.padding-xspace.4 (16px)16px12px20px
*.control.padding-yspace.2 (8px)8px4px ¹12px ²
*.control.gapspace.2 (8px)8px4px12px
*.layout.sectionspace.8 (32px)32px24px40px
*.layout.componentspace.5 (20px)20px12px ³28px

Toutes les valeurs sont des multiples de 4. Zéro exception.

¹ 8 × 0.75 = 6 → floor(6/4)×4 = 4px ² 8 × 1.25 = 10 → ceil(10/4)×4 = 12px ³ 20 × 0.75 = 15 → floor(15/4)×4 = 12px ⁴ 20 × 1.25 = 25 → ceil(25/4)×4 = 28px

Groupes de tokens et compatibilité

semantic.space.control.* et semantic.space.layout.* restent les alias normaux (densité par défaut). component.json continue de les référencer sans changement.

Pour utiliser une densité différente, on référence explicitement le groupe :


Argumentaire

Pourquoi floor pour compact et ceil pour comfortable ?

Pourquoi des groupes explicites plutôt qu'un facteur unique swappable ?

La v1 utilisait un facteur unique. Son problème : le même token ne peut pas encoder floor() pour compact et ceil() pour comfortable — la formule doit connaître la direction d'arrondi à l'avance.

Trois groupes explicites :

Pourquoi les facteurs 0.75 / 1.0 / 1.25 ?

Mémorisables (−25%, ±0%, +25%), perceptuellement significatifs (Nielsen : 25% de changement = perceptible mais non perturbant), et ils produisent des multiples de 4 après floor/ceil dans tous les cas de notre échelle.

Pourquoi des math tokens et pas des CSS variables ?

Les math tokens sont résolus à la compilation (Style Dictionary), produisant du CSS statique portable (iOS, Android, emails). Les CSS variables nécessitent un mécanisme runtime supplémentaire.


Alternatives rejetées

AlternativeRaison du rejet
Facteur unique swappable (v1)Impossible d'encoder floor/ceil directionnels dans une seule formule — valeurs hors grille garanties
round() seul sans floor/ceilRésultats imprévisibles selon la position sur la grille (ex: round(6/4)*4 = 8px ≠ réduit)
CSS custom property --densityNon portable (iOS/Android/email), nécessite infra CSS runtime
Classes utilitaires (density-compact)Sort du système de gouvernance token
Valeurs fixes pré-calculées (sans math)Perd la traçabilité entre base et densité — un changement de base ne se propage plus

Conséquences

Pour les tokens :

Pour les agents IA :

Pour les équipes :

Pour Tokens Studio :

Risques :

← ADR-024 ADR-026 →