La classe Tailwind qui ne s'appliquait jamais

Tailwind la générait, TypeScript la validait, ESLint ne disait rien. Elle n'avait juste aucun effet. Le bug venait du nom que j'avais choisi.

RM
Rabie Menad
3 min de lecture

En ajoutant une échelle typographique à ce site, j'ai écrit une classe qui n'a jamais rien changé à l'écran.

Aucun outil ne s'en est plaint. Tout était vert. Il a fallu ouvrir l'inspecteur pour comprendre.

Le point de départ

Trois tailles de police traînaient en dur dans le code : text-[11px], text-[10px], text-[0.8125rem]. Objectif banal : les remplacer par des tokens du design system.

Tailwind 4 se configure directement en CSS, donc l'ajout est trivial :

@theme inline {
  --text-3xs: 0.625rem;
  --text-2xs: 0.6875rem;
  --text-xs-plus: 0.8125rem;
}

Trois tokens, trois remplacements. pnpm typecheck, pnpm lint:ci, pnpm test, pnpm build : tout au vert.

Le symptôme

Un seul des trois ne changeait rien. Le sommaire, censé afficher ses sous-titres en 13px, restait à 14px.

La classe était pourtant bien dans le JSX :

className={cn(
  'text-sm transition-colors',
  entry.level === 3 && 'text-xs-plus ps-6',
)}

Ma première hypothèse, et pourquoi elle était fausse

J'ai d'abord pensé à tailwind-merge, qui aurait supprimé la classe en la prenant pour un doublon de text-sm.

Logique. Et faux.

Le DOM racontait autre chose :

"relative flex items-baseline ... transition-colors text-sm ps-6"

ps-6 était là, donc la condition s'évaluait bien. Mais text-xs-plus avait disparu et text-sm était resté. Si tailwind-merge avait fait son travail de déduplication, c'est l'inverse qui aurait dû se produire.

Le test qui tranche

Plutôt que de continuer à supposer, j'ai interrogé la bibliothèque directement :

node -e "
const { twMerge } = require('tailwind-merge');
console.log(twMerge('text-sm', 'text-xs-plus'));
console.log(twMerge('text-sm', 'text-2xs'));
"

Résultat :

text-sm text-xs-plus
text-2xs

La deuxième ligne montre le comportement attendu : text-2xs remplace text-sm.

La première ne supprime rien. Elle garde les deux classes.

L'explication

tailwind-merge doit reconnaître que deux classes appartiennent au même groupe pour arbitrer entre elles. Il connaît les motifs standard de l'échelle (xs, sm, 2xs, 3xs), mais xs-plus ne ressemble à rien qu'il sache classer. Ne pouvant l'identifier comme une taille de police, il le laisse passer à côté de text-sm.

Les deux se retrouvent donc dans le DOM, avec la même spécificité CSS. C'est l'ordre dans la feuille de style qui décide, et Tailwind trie ses utilitaires par taille croissante. text-sm (14px) étant généré après text-xs-plus (13px), il gagne.

Le bug ne venait ni de Tailwind, ni de tailwind-merge. Il venait du nom que j'avais choisi.

Le correctif

Renommer le token pour qu'il suive un motif reconnu :

/* `2sm` (et non `xs-plus`) : tailwind-merge ne traite que la forme
   `<n>xs` / `<n>sm` comme une taille de police */
--text-2sm: 0.8125rem;
--text-2sm--line-height: 1.5;

Vérification dans le navigateur, cette fois :

getComputedStyle(h3).fontSize // "13px"
getComputedStyle(h2).fontSize // "14px"

Ce que j'en retiens

Un nom n'est jamais neutre

Dès qu'on utilise tailwind-merge, donc cn(), donc n'importe quel projet shadcn/ui, le nom d'un token custom devient une contrainte technique. Il doit rester dans les motifs que la bibliothèque sait classer.

On croit nommer pour des humains. On nomme aussi pour un parseur.

Aucun outil ne pouvait le voir

OutilCe qu'il vérifieVerdict
TypeScriptLes types, pas les chaînes CSSMuet
ESLintLes règles de codeMuet
TailwindLe token existe, la classe est généréeMuet
Le buildLa compilation aboutitMuet

Chacun faisait correctement son travail. Aucun n'avait le contexte : la question n'était pas « cette classe existe-t-elle ? » mais « cette classe l'emporte-t-elle ? ».

Seul le navigateur tranche

C'est la vraie leçon, et elle dépasse Tailwind. Une suite verte prouve que le code compile, pas qu'il fait ce qu'on croit.

Pour du CSS, la vérification tient en une ligne :

getComputedStyle(element).fontSize

Trente secondes. C'est moins cher que le doute.

RM
Rabie MenadDéveloppeur freelance

Je conçois et livre des produits web performants en React & Next.js. Parlons de votre projet.

Plus d'articles