@mostajs/palette
v0.2.0
Published
Composition de palettes accessibles (zéro dépendance) : contraste WCAG, dérivation de tokens sémantiques garantis lisibles, audit et sortie CSS. Peu d'entrées, beaucoup de dérivation, une porte de validation — un sélecteur de couleur ne sait pas dire non,
Maintainers
Readme
@mostajs/palette
Auteur : Dr Hamid MADANI [email protected] Licence : AGPL-3.0-or-later · Zéro dépendance · ESM
Composition de palettes accessibles. On n'y saisit pas dix-neuf couleurs : on en donne trois de marque et quatre d'état, et tout le reste — surfaces, textes, filets, survols, anneau de focus, fonds d'alerte — est dérivé, puis validé contre les seuils WCAG.
Un sélecteur de couleur ne sait pas dire non. Ce module le sait. C'est toute sa raison d'être : les bordures invisibles (1,5:1), les ors illisibles (2,1:1) et les gris hors seuil ne naissent pas d'une négligence — ils naissent d'un outil qui accepte tout. Ici,
derive()corrige une couleur qui n'atteint pas son seuil au lieu de l'émettre telle quelle.
Le principe : nommer par la fonction, pas par la couleur
Tant qu'un token s'appelle --gold, quelqu'un finira par l'utiliser comme couleur de texte — et il sera à 2:1 sur blanc. S'il s'appelle --text, --line, --field-border ou --focus-ring, la question ne se pose plus : la valeur est dérivée et garantie.
Usage
import { compose } from '@mostajs/palette';
const { tokens, report, css } = compose({ brand: '#06284A', accent: '#C9952C' });
report.pass; // true — aucun couple sous son seuil
css; // ':root{--text:#0F172A;--line:…;--focus-ring:#bc8b29;--radius-md:8px;…}'API
| Fonction | Rôle |
|---|---|
| contrast(a, b) | rapport WCAG (1 → 21) |
| check(fg, bg, { size }) | verdict : { ratio, aa, aaa, level } — size = normal (4,5) · large (3) · ui (3, bordures et icônes) |
| mix / lighten / darken / scale | manipulation et rampe 50→900 |
| readableOn(bg) | la couleur de texte lisible sur ce fond |
| ensureContrast(c, bg, { min }) | la porte : corrige c jusqu'au seuil (fond clair → fonce ; fond sombre → éclaircit) |
| derive(input) | le cœur : entrées → tokens sémantiques garantis lisibles |
| audit(tokens) | la table de contrastes (les 16 couples exigés par CONTRACTS) |
| toCss(tokens) | bloc :root{…} — couleurs + échelles (rayon, espace, ombre, mouvement, typographie) |
| compose(input, { strict }) | façade : { input, tokens, report, css } |
Filet décoratif ≠ contour porteur
Deux tokens, et la distinction est normative :
--line— séparateur, contour de carte : décoratif, aucun seuil imposé, il reste léger (comme un#CBD5E1).--field-border— contour de champ, limite cliquable : il porte une information, donc ≥ 3:1 (WCAG 1.4.11).
Les confondre, c'est soit alourdir tout le design, soit rendre les formulaires invisibles.
Garanties (testées)
- Vecteurs de référence WCAG : noir/blanc = 21:1 ·
#767676sur blanc = 4,54:1 (la limite AA officielle) ·#949494= 3,0:1 → échec. - Invariant central : pour 24 combinaisons de marque/accent volontairement hostiles (jaunes pâles, gris clairs),
audit(derive(...))passe toujours. Aucune palette illisible n'est productible. - L'anneau de focus est un token de première classe : toujours ≥ 3:1, même à partir d'un accent pâle.
- Le texte des alertes est validé sur le fond d'alerte, pas sur le blanc.
28 tests verts (@mostajs/mjs-unit) : npm test.
Échelles fournies
Grille de 8 px (8/16/24/32/48/64) · rayons 4/8/12/16/24 · trois ombres · deux durées + une courbe · typographie 32/24/20/16/14/12/11 avec graisses et interlignes.
