La bible de la nomenclature CSS
Non pas « comment nommer », mais quand nommer, quand refuser de nommer, et comment vivre avec ses noms dans deux ans.
💡 Le principe qui gouverne tout le reste : une nomenclature ne sert pas à décrire ce qu'un élément ressemble, mais ce qu'il est et à quoi il sert. Un nom qui décrit l'apparence devient un mensonge dès la première refonte.
Les quatre familles de classes
Comment lire ce document
L'ordre du menu est un ordre d'apprentissage, pas un simple classement. Si tu débutes, lis dans l'ordre. Les trois pages qui font vraiment la différence entre un débutant et un pro sont, dans l'ordre :
- Décider de nommer — parce que le plus dur n'est pas de nommer, c'est de résister à l'envie de nommer.
- Anti-patterns — parce qu'on apprend plus vite de ce qui casse que de ce qui marche.
- Cascade & spécificité — parce que c'est la source n°1 de frustration, et que sans ça on finit en guerre de
!important.
Le squelette d'une page bien nommée
l-section ← le layout pose la structure
└─ l-container ← centre et limite la largeur
└─ l-grid ← répartit les enfants
├─ c-card ← un composant autonome
│ ├─ c-card__img ← son élément enfant (BEM)
│ └─ c-card__title
├─ c-card c-card--featured ← une variation (BEM)
└─ c-card is-loading ← un état temporaire
Décider de nommer
La compétence la plus rare, et celle qu'aucune documentation n'enseigne : savoir ne pas créer une classe.
La règle de trois
Empruntée au refactoring logiciel, elle s'applique parfaitement au CSS :
Ne nomme rien. Style local sur l'élément, dans Elementor. Une classe globale utilisée une fois est un coût de maintenance sans contrepartie.
Attends encore. Deux occurrences peuvent être une coïncidence — deux choses qui se ressemblent aujourd'hui mais évolueront différemment demain. Duplique le style, note-le mentalement.
Maintenant tu nommes. Trois occurrences, ce n'est plus une coïncidence : c'est un motif. Crée la classe globale, remplace les trois instances.
L'arbre de décision complet
Avant de créer une classe, pose-toi ces questions dans cet ordre :
Espacement entre enfants → réglage Gap du Container. Hiérarchie de titres → Typographie globale. Couleur de marque → Variable. Si oui : ne crée pas de classe, utilise l'outil natif. Une classe qui double une fonctionnalité native est une dette pure.
Un objet (une carte, un bouton) → composant c-. Un attribut isolé (centrer, masquer) → utilitaire u-. Une structure qui contient d'autres choses → layout l-. Si tu n'arrives pas à trancher, c'est souvent que ton élément fait trop de choses : découpe-le.
Permanent (c'est une sorte de bouton) → variation BEM --primary. Temporaire (il est actuellement désactivé) → état is-disabled. Un état ne se met jamais dans le nom du composant.
Fais le test mentalement : « si demain le client passe du bleu au vert, ce nom ment-il ? ». Si oui, tu as nommé l'apparence. Recommence en nommant le rôle.
Le test du nouveau venu
Le meilleur juge de la qualité d'un nom : un développeur qui découvre le projet.
Peut-il, en lisant uniquement la liste des classes, sans ouvrir une seule page, deviner à quoi ressemble le site ? Si oui, ta nomenclature est bonne. Si les noms sont .box-2, .custom-thing, .section-new, il ne peut rien deviner — et toi non plus, dans six mois.
Conventions d'écriture
Les règles ennuyeuses. Ce sont exactement celles qui font diverger un projet quand on ne les fixe pas au départ.
1. La langue : anglais, toujours
.c-bouton-principal.l-entete
Mélange deux langues avec le CSS lui-même (qui est en anglais), et devient illisible pour tout développeur tiers.
.c-btn-primary.l-header
Cohérent avec le CSS, les frameworks, la documentation Elementor, et n'importe quel dev que tu embaucheras.
Ton interface peut être en français. Tes noms de classes, non. C'est du code.
2. La casse : kebab-case, uniquement
| Forme | Exemple | Verdict |
|---|---|---|
| kebab-case | .c-btn-primary | ✓ La norme CSS. Utilise ça. |
| camelCase | .cBtnPrimary | ✗ Convention JavaScript, pas CSS. |
| PascalCase | .CBtnPrimary | ✗ Convention de composant React. |
| snake_case | .c_btn_primary | ✗ Entre en conflit visuel avec le __ de BEM. |
3. Les séparateurs BEM
.c-card ← le bloc .c-card__title ← un élément (double underscore) .c-card--featured ← une variation (double tiret) .c-card__title--big ← une variation d'élément /* JAMAIS de chaînage d'éléments */ .c-card__body__title ✗ ← le DOM peut changer, ce nom casse .c-card__title ✓ ← plat, robuste
.c-card__title, pas .c-card__header__row__title. Le nom décrit l'appartenance au bloc, pas le chemin dans le DOM.4. Les abréviations autorisées
Une abréviation n'est acceptable que si elle est universellement comprise. Dans le doute, écris le mot en entier.
btn · nav · img · imgcol · msg · alt
Tout le monde les lit sans réfléchir.
ctn · wrp · hdrbg2 · txt-sm-b
Tu économises 4 caractères et tu perds 30 secondes à chaque relecture.
5. Singulier ou pluriel ?
Règle simple : l'objet est au singulier, la collection au pluriel.
.c-card ← un objet carte .l-cards ← la grille qui contient plusieurs cartes .c-nav-item ← un item de navigation .c-nav ← la navigation entière
6. Ordre des mots : du général au spécifique
.c-primary-btn.c-large-card-featured
Impossible à trier alphabétiquement de façon utile. Les boutons ne sont pas regroupés.
.c-btn--primary.c-card--featured
Tri alphabétique = regroupement logique. Tous les c-btn se suivent dans le Class Manager.
c- Composant
Un objet autonome, auto-contenu, qui fonctionne indépendamment de son environnement.
Le test du composant
Une classe mérite le préfixe c- si elle passe ce test : « puis-je découper cet élément, le coller ailleurs sur le site, et attendre qu'il fonctionne et reste beau ? »
Si oui → composant. Si non (il dépend de son parent pour sa largeur, son alignement, sa position) → ce n'est pas un composant, c'est du layout ou un élément enfant.
Exemples canoniques
.c-btn— un bouton.c-card— une carte de contenu.c-badge— une étiquette.c-alert— un message d'alerte.c-modal— une fenêtre modale.c-nav— une navigation.c-input— un champ de formulaire.c-avatar— une photo de profil
Anatomie BEM d'un composant
<article class="c-card c-card--featured"> <img class="c-card__img" src="..."> <h3 class="c-card__title">Titre</h3> <p class="c-card__text">Description</p> <a class="c-btn c-btn--secondary">Lire plus</a> </article>
| Partie | Syntaxe | Ce que ça veut dire |
|---|---|---|
| Bloc | .c-card | L'objet lui-même. Il vit seul. |
| Élément | .c-card__title | Une partie du bloc. Elle n'a pas de sens hors du bloc. |
| Variation | .c-card--featured | Une sorte de carte. Permanente, pas dynamique. |
Le composant ne se positionne pas lui-même
.c-card qui contient margin-bottom: 2rem ne sera plus réutilisable dans une grille qui gère déjà son gap..c-card {
margin-bottom: 2rem;
width: 300px;
float: left;
}
Impossible à réutiliser ailleurs sans se battre contre ces règles.
.c-card {
padding: 1.5rem;
border-radius: 8px;
background: var(--color-surface);
}
/* le parent gère le reste */
.l-grid { gap: 2rem; }
Réutilisable partout. Le parent décide de l'espacement.
l- Layout
La structure invisible qui organise l'espace. Elle ne se voit pas — elle se ressent.
Ce qu'un layout gère (et rien d'autre)
Display (flex, grid) · Gap · Padding · Largeur max · Alignement des enfants · Nombre de colonnes · Comportement responsive
Couleur de fond décorative · Typographie · Bordures · Ombres · Tout ce qui relève de l'apparence plutôt que de la structure
Exemples canoniques
.l-section— une bande horizontale pleine largeur.l-container— centre et limite la largeur du contenu.l-grid— répartit des enfants en colonnes.l-stack— empile des enfants verticalement avec un gap.l-cluster— aligne des enfants horizontalement, avec retour à la ligne.l-sidebar— contenu principal + colonne latérale
Les trois layouts qui couvrent 90% des besoins
/* 1. STACK — empiler verticalement (le plus utilisé) */
.l-stack {
display: flex;
flex-direction: column;
gap: var(--space-4);
}
/* 2. CLUSTER — aligner horizontalement, avec retour à la ligne */
.l-cluster {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
align-items: center;
}
/* 3. GRID — répartir en colonnes égales */
.l-grid {
display: grid;
gap: var(--space-6);
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
auto-fit + minmax : cette seule ligne rend ta grille responsive sans une seule media query. Les colonnes se réorganisent toutes seules selon la place disponible. C'est le genre de règle qui remplace 40 lignes de breakpoints.Le layout dans Elementor v4
Le Flexbox Container d'Elementor est ta classe layout. Il expose nativement direction, gap, alignement, padding — exactement le domaine du l-.
l- personnalisées dans Elementor. Configure le Container nativement et lie ses valeurs à tes variables. Ne crée une classe l- que si la même configuration structurelle se répète 3+ fois (cf. règle de trois).u- Utilitaire
Une classe, une propriété, une responsabilité. Puissant — et dangereux si mal dosé.
La définition stricte
Un utilitaire fait une seule chose, ne dépend de rien, et fonctionne partout. S'il fait deux choses, ce n'est plus un utilitaire, c'est un mini-composant mal nommé.
/* ✓ Utilitaires — une propriété chacun */
.u-text-center { text-align: center; }
.u-hidden { display: none; }
.u-mb-4 { margin-bottom: var(--space-4); }
/* ✗ Ce n'est plus un utilitaire */
.u-fancy-box {
padding: 1rem;
border: 1px solid;
border-radius: 8px;
background: white;
} ← ça, c'est un composant. Appelle-le .c-box.
Le grand danger : le style inline déguisé
<div class="u-p-4 u-mb-6 u-bg-white u-rounded u-shadow u-border u-flex u-flex-col u-gap-3">
Ce motif se répète sur 12 pages. Le jour où le padding change, tu édites 12 pages.
<div class="c-card">
/* et une seule fois : */
.c-card {
padding: var(--space-4);
/* ...etc */
}
Un seul point de modification. C'est ça, un design system.
Le seuil de bascule
Quand l'utilitaire est le bon choix
- Une exception ponctuelle — ce titre-là, et seulement lui, est centré.
- Un ajustement qui ne mérite pas un composant — masquer un élément sur mobile.
- Une composition rapide en phase de prototypage — tu explores, tu nommeras plus tard.
Dans tous les autres cas, demande-toi si le composant ne devrait pas porter ce style lui-même.
is- / has- États
La famille oubliée — et pourtant indispensable dès qu'il y a un menu, un accordéon ou un formulaire.
Variation ≠ état
C'est la distinction la plus mal comprise de tout BEM. Elle est pourtant simple :
Variation (--) | État (is- / has-) | |
|---|---|---|
| Nature | Permanente, décidée au design | Temporaire, change à l'exécution |
| Qui la pose | Toi, en construisant la page | Le JavaScript, en réponse à une action |
| Question | « C'est quelle sorte de bouton ? » | « Dans quelle condition est-il, là, maintenant ? » |
| Exemple | .c-btn--primary | .is-loading |
La convention
/* is- : l'élément EST dans cet état */ .is-active ← onglet sélectionné, lien de nav courant .is-open ← menu déplié, accordéon ouvert .is-hidden ← masqué dynamiquement .is-loading ← en attente d'une réponse .is-disabled ← non cliquable /* has- : l'élément CONTIENT quelque chose */ .has-error ← un champ dont la validation a échoué .has-children ← un item de menu avec un sous-menu .has-icon ← un bouton qui contient une icône
L'état est un modificateur global, pas un préfixe de bloc
.c-menu--open.c-btn-disabled
Tu devras dupliquer --open pour chaque composant. Et le JS devra connaître le nom du composant pour poser la classe.
.c-menu.is-open.c-btn.is-disabled
Une seule classe .is-open pour tout le site. Le JS fait el.classList.toggle('is-open'), quel que soit le composant.
En CSS
/* On scope l'état au composant, pour éviter les collisions */
.c-menu.is-open {
max-height: 500px;
opacity: 1;
}
.c-input.has-error {
border-color: var(--color-danger);
}
.c-btn.is-loading {
pointer-events: none;
opacity: 0.6;
}
is-, has-) fait qu'un développeur front sait immédiatement quelles classes sont manipulées dynamiquement et lesquelles sont statiques. Sans ça, chaque interaction devient une enquête.Gap, margin, padding
Le sujet qui gâche le plus de sites. Trois outils, une hiérarchie stricte.
1. Les trois outils
| Outil | Ce qu'il fait | Quand l'utiliser |
|---|---|---|
| Padding | Espace à l'intérieur, entre le contenu et le bord | Donner de l'air au contenu d'un composant ou d'une section |
| Gap | Espace entre les enfants, posé sur le parent | Le réflexe par défaut dès qu'il y a plusieurs enfants |
| Margin | Espace entre deux éléments, posé sur l'élément | L'exception. Un ajustement ponctuel qu'aucun des deux autres ne couvre |
2. La hiérarchie de décision
Si plusieurs éléments doivent être espacés régulièrement, c'est le parent qui décide, une fois, pour tous. Zéro marge sur les enfants.
Pour l'air intérieur — entre le bord d'une carte et son contenu, entre le bord d'une section et sa grille.
Uniquement pour un cas isolé qui échappe aux deux précédents. Si tu poses des marges partout, tu as raté l'étape 1.
3. Pourquoi le gap a gagné
.c-card { margin-bottom: 2rem; }
.c-card:last-child {
margin-bottom: 0; /* la rustine */
}
Il faut toujours une exception pour le dernier. Et si l'ordre change en responsive, ça casse.
.l-grid {
display: grid;
gap: 2rem;
}
Le gap ne s'applique qu'entre les enfants. Jamais avant le premier, jamais après le dernier. Aucune rustine.
4. L'échelle d'espacement
Aucune valeur au hasard. Une échelle unique, et chaque espacement pointe vers un cran.
| Cran | rem | px | Usage typique |
|---|---|---|---|
--space-0 | 0 | 0 | Réinitialisation |
--space-1 | 0.25rem | 4px | Micro — icône collée à son texte |
--space-2 | 0.5rem | 8px | Serré — entre des badges |
--space-3 | 0.75rem | 12px | Un label et son champ |
--space-4 | 1rem | 16px | Le cran de base — le plus fréquent |
--space-5 | 1.5rem | 24px | Entre deux blocs de texte |
--space-6 | 2rem | 32px | Entre deux composants (cartes) |
--space-8 | 3rem | 48px | Padding intérieur d'une section |
--space-10 | 4rem | 64px | Entre deux grandes sections |
5. Et les classes u- d'espacement ?
u-mb-4 & consorts sont réservées aux exceptions ponctuelles. Si tu utilises les deux systèmes en parallèle sans règle, tu ne sauras plus jamais d'où vient un espacement.Nomenclature, si tu en crées malgré tout :
.u-p-4 padding sur les 4 côtés .u-pt-4 padding-top .u-pb-4 padding-bottom .u-px-4 padding horizontal (left + right) .u-py-4 padding vertical (top + bottom) .u-m-4 margin sur les 4 côtés .u-mt-4 margin-top .u-mb-4 margin-bottom .u-mx-auto centrage horizontal d'un bloc à largeur fixe .u-gap-4 gap uniforme .u-gap-x-4 column-gap .u-gap-y-4 row-gap
Typographie
La même logique d'échelle, appliquée au texte — avec les raisons UX derrière chaque valeur.
1. Taille de texte
| Classe | px | rem | Usage |
|---|---|---|---|
.u-text-xs | 12px | 0.75rem | Mentions légales — jamais pour du texte à lire |
.u-text-sm | 14px | 0.875rem | Légendes, texte secondaire |
.u-text-base | 16px | 1rem | Corps de texte — le minimum confortable |
.u-text-lg | 18px | 1.125rem | Chapô, texte d'introduction |
.u-text-xl | 20px | 1.25rem | Sous-titres |
.u-text-2xl | 24px | 1.5rem | Titres de section (H3) |
.u-text-3xl | 30px | 1.875rem | Titres principaux (H2) |
.u-text-4xl | 36px | 2.25rem | Titre héros (H1) |
.u-text-5xl | 48px | 3rem | Grande accroche |
2. Graisse
| Classe | Valeur | Usage |
|---|---|---|
.u-font-light | 300 | Grands titres uniquement — illisible en petit |
.u-font-regular | 400 | Corps de texte |
.u-font-medium | 500 | Accent léger : labels, boutons |
.u-font-semibold | 600 | Sous-titres |
.u-font-bold | 700 | Titres, appels à l'action |
3. Hauteur de ligne — la règle inversée
| Classe | Valeur | Pour quelle taille |
|---|---|---|
.u-leading-tight | 1.1 – 1.2 | Grands titres (30px et +) |
.u-leading-snug | 1.35 | Sous-titres, textes courts |
.u-leading-normal | 1.5 | Corps de texte — la valeur sûre |
.u-leading-relaxed | 1.65 – 1.75 | Longs paragraphes, articles |
4. Espacement des lettres
.u-tracking-tight | -0.02em | Grands titres — les grandes lettres paraissent trop espacées, on resserre |
.u-tracking-normal | 0 | Texte courant |
.u-tracking-wide | 0.05em | Obligatoire sur les majuscules — sans ça, un surtitre en capitales est illisible |
5. La typographie fluide (clamp)
Pour les titres, oublie les tailles fixes par breakpoint. Un titre doit grandir continûment avec l'écran, pas par sauts brutaux.
/* Un H1 qui passe de 32px (mobile) à 56px (desktop), de façon fluide, sans une seule media query */ --font-size-h1: clamp(2rem, 1.2rem + 3.5vw, 3.5rem);
6. Hiérarchie de titres : pas des classes u-
.u-text-4xl et l'appliquer à la main sur chaque H1. Non. La hiérarchie H1→H6 d'un site se définit une fois dans les Réglages du site → Typographie d'Elementor. Les classes u-text-* servent aux exceptions — un chiffre-clé mis en avant, un chapô plus grand. Pas à la structure du document.Cascade & spécificité
« Pourquoi ma classe ne s'applique pas ?! » — la frustration n°1, et sa résolution définitive.
💡 Le C de CSS veut dire « Cascading ». Ce n'est pas un détail : c'est le cœur du langage. Quand deux règles se contredisent, il faut un arbitre. Cet arbitre s'appelle la spécificité, et il obéit à des règles strictes — pas à ton intuition.
1. L'ordre de priorité (du plus faible au plus fort)
| Rang | Type de règle | Exemple | Poids |
|---|---|---|---|
| 1 | Sélecteur de balise | h1 { } | 0-0-1 |
| 2 | Classe | .c-title { } | 0-1-0 |
| 3 | Deux classes | .c-card .c-title { } | 0-2-0 |
| 4 | ID | #header { } | 1-0-0 |
| 5 | Style inline | style="..." | 1-0-0-0 |
| 6 | !important | color: red !important; | Écrase presque tout |
La lecture du « poids » : on compare de gauche à droite. Un seul ID bat n'importe quel nombre de classes. C'est pour ça qu'on évite les ID en CSS — ils sont impossibles à surcharger proprement.
2. À égalité, le dernier gagne
.c-btn { background: blue; }
.c-btn { background: green; } ← c'est celle-ci qui s'applique
Deux règles de même spécificité ? Celle qui arrive plus bas dans la feuille de style l'emporte. D'où l'importance de l'ordre de chargement de tes fichiers CSS.
3. Le cas Elementor v4
C'est là que ça devient concret. Dans Elementor v4, plusieurs sources de style cohabitent sur un même élément :
| Source | Priorité | Remarque |
|---|---|---|
| Réglages globaux du site (typographie, couleurs) | La plus faible | La base sur laquelle tout se construit |
Classe globale (.c-btn) | Moyenne | Ce que tu crées dans le Class Manager |
| Classe locale de l'élément | Plus forte | Générée automatiquement par Elementor quand tu stylises un widget directement |
| CSS personnalisé de l'élément | La plus forte | À réserver aux cas vraiment particuliers |
4. !important : le symptôme, pas la solution
Quand tu écris !important, tu ne résous pas un problème de style. Tu résous un problème d'architecture — mal.
.c-btn {
background: red !important;
}
Ça marche aujourd'hui. Demain, quelqu'un devra écrire un autre !important pour te surcharger. Puis un troisième. C'est une escalade sans fin.
Demande-toi pourquoi ta règle est écrasée. Un style local Elementor ? Un ID ? Un sélecteur trop imbriqué ? Enlève la cause plutôt que de la contourner.
5. Règle d'or : garde la spécificité basse et plate
.c-card__title plutôt qu'en écrivant .c-card .content .header h3, tu obtiens le même ciblage avec une spécificité de 0-1-0 au lieu de 0-3-1. Résultat : tes règles restent faciles à surcharger quand tu en as besoin, sans jamais recourir à !important. La nomenclature n'est pas qu'une question de lisibilité — c'est un outil de contrôle de la spécificité.Anti-patterns
Ce qui trahit un débutant. On apprend plus vite de ce qui casse que de ce qui marche.
.c-btn-blue · .u-text-red · .c-box-shadow-big
Pourquoi c'est cassé : le jour où le client passe au vert, tu as deux options — garder une classe blue qui affiche du vert (un mensonge inscrit dans le code), ou renommer 40 occurrences. Les deux sont mauvaises.
La correction : .c-btn--primary. Le rôle ne change pas, même si la couleur change.
.c-left-box · .l-right-column · .c-top-banner
Pourquoi c'est cassé : en responsive, ta colonne de droite passe en dessous. Elle s'appelle toujours right. Le nom devient un piège pour la personne suivante.
La correction : nomme la fonction — .l-sidebar, .c-banner--promo.
class="u-p-4 u-mb-6 u-bg-white u-rounded u-shadow u-flex u-gap-3"
Pourquoi c'est cassé : tu as réinventé le style inline, en plus long. Ce bloc se répète sur 12 pages ; le jour où le padding change, tu édites 12 pages. Tu n'as gagné aucune réutilisabilité.
La correction : au-delà de 3 utilitaires empilés qui se répètent, nomme le motif. C'est un composant.
.c-card-2 · .c-card-new · .c-card-final · .c-card-final-v2
Pourquoi c'est cassé : ces noms n'existent que parce qu'on n'a pas osé toucher à l'original. Personne ne saura jamais laquelle est la bonne. C'est de la dette pure.
La correction : si c'est une variation → .c-card--compact. Si l'ancienne est morte → supprime-la (voir Cycle de vie).
!important par réflexePourquoi c'est cassé : c'est un symptôme, pas une solution. Il signale que ton architecture de spécificité est cassée quelque part. Chaque !important en appelle un autre pour le surcharger. L'escalade est sans fin.
La correction : trouve pourquoi ta règle est écrasée (style local Elementor ? ID ? imbrication ?) et supprime la cause.
.c-card { margin-bottom: 2rem; width: 300px; }
Pourquoi c'est cassé : ta carte n'est plus réutilisable. Dans une grille qui gère déjà son gap, elle ajoute une marge parasite. Dans un contexte plus étroit, sa largeur fixe déborde.
La correction : le composant gère son intérieur. Le parent gère son extérieur.
.c-card__body__header__title
Pourquoi c'est cassé : BEM est plat. Tu viens d'inscrire la structure du DOM dans un nom de classe. Le jour où tu supprimes le body, le nom ment.
La correction : .c-card__title. Peu importe la profondeur, l'élément appartient au bloc, point.
Créer .u-gap-4 alors que le Container Elementor a un réglage Gap natif.
Pourquoi c'est cassé : deux sources de vérité pour la même chose. Dans six mois, tu ne sauras plus si l'espacement d'une section vient du réglage natif ou d'une classe. Le débogage devient un cauchemar.
La correction : une seule source par type de décision. Structurel → natif. Exception → classe.
.c-menu--open au lieu de .c-menu.is-open
Pourquoi c'est cassé : tu devras créer un --open pour chaque composant qui s'ouvre. Et ton JavaScript devra connaître le nom du composant pour poser la bonne classe.
La correction : .is-open, une seule classe universelle, posée par le JS sur n'importe quel composant.
Cas pratique commenté
Une section réelle, du brief au code — avec chaque décision expliquée à voix haute. Les taxonomies s'oublient ; un raisonnement se retient.
Le brief : une section « Nos services » avec un titre, un chapô, et trois cartes (icône, titre, description, lien). Sur desktop, les cartes sont côte à côte. Sur mobile, elles s'empilent. Une des trois cartes est mise en avant.
Étape 1 — Je résiste à l'envie de nommer
Premier réflexe du débutant : ouvrir le Class Manager et créer .services-section, .services-title, .services-grid, .service-card… Stop.
Question : cette section « Nos services » existe-t-elle ailleurs sur le site ? Non, elle est unique à la page d'accueil.
Décision : je ne crée aucune classe spécifique à « services ». Une classe utilisée une fois est un coût sans contrepartie. Je vais chercher les motifs génériques qui se cachent derrière.
Étape 2 — Je cherche les motifs réutilisables
Je regarde la maquette et je me demande : qu'est-ce que je reverrai ailleurs sur ce site ?
- Une bande horizontale avec du padding vertical → je la reverrai sur chaque section. C'est
l-section. - Une grille de cartes qui s'empile en mobile → je la reverrai (portfolio, témoignages, articles). C'est
l-grid. - Une carte avec icône, titre, texte, lien → oui, c'est le motif le plus réutilisable de tous. C'est
c-card. - Un lien stylé en bouton → partout. C'est
c-btn.
Étape 3 — Le titre et le chapô : je ne nomme rien
Question : ai-je besoin d'une classe .c-section-title ?
Réponse : non. Un H2 est un H2. Sa taille, sa graisse et sa couleur sont déjà définies dans les Réglages du site → Typographie d'Elementor. Créer une classe pour ça, ce serait doubler une fonctionnalité native — l'anti-pattern n°8.
Le chapô, lui, est un paragraphe légèrement plus grand que le corps. Une exception ponctuelle → une classe utilitaire suffit : .u-text-lg. Pas de composant.
Étape 4 — L'espacement : je ne pose aucune marge
Le piège : mettre margin-bottom: 2rem sur le titre, puis sur le chapô, puis sur chaque carte.
Ce que je fais à la place : le container de la section est un l-stack avec un gap. Il espace automatiquement le titre, le chapô et la grille. La grille, elle, a son propre gap entre les cartes.
Résultat : zéro marge dans tout le bloc. Deux valeurs de gap, posées sur deux parents. Si le client trouve que « ça manque d'air », je change une valeur.
Étape 5 — La carte mise en avant : variation, pas nouveau composant
Question : est-ce que je crée .c-featured-card ?
Non. C'est la même carte, avec un fond différent. Ce n'est pas un autre objet, c'est une sorte de carte. Donc : une variation BEM → .c-card--featured.
Et si le client veut plus tard la mettre en avant dynamiquement au survol ? Là, ce serait un état → .is-highlighted. La distinction : la variation est décidée au design, l'état survient à l'exécution.
Le résultat
<section class="l-section">
<div class="l-container l-stack">
<h2>Nos services</h2>
<p class="u-text-lg">Une phrase d'introduction.</p>
<div class="l-grid">
<article class="c-card">
<div class="c-card__icon">...</div>
<h3 class="c-card__title">Design</h3>
<p class="c-card__text">Description.</p>
<a class="c-btn c-btn--ghost">En savoir plus</a>
</article>
<article class="c-card c-card--featured">
... même structure ...
</article>
<article class="c-card">
... même structure ...
</article>
</div>
</div>
</section>
Le bilan
| Ce que j'ai créé | Réutilisable ailleurs ? |
|---|---|
l-section, l-container, l-stack, l-grid | Sur toutes les sections du site |
c-card + ses éléments + --featured | Portfolio, blog, témoignages… |
c-btn + --ghost | Partout |
u-text-lg | Chaque chapô du site |
Aucune classe ne mentionne « services ». J'ai construit une section unique avec un vocabulaire entièrement générique. C'est la marque d'un design system qui tient.
Fondements UX
Pourquoi les échelles ne sont pas des chiffres au hasard. Chaque valeur répond à une contrainte de perception humaine.
💡 Sans cette page, tes échelles ressemblent à des nombres arbitraires. Avec elle, tu comprends que ce sont des contraintes de lisibilité déguisées en tokens — et tu sauras les défendre face à un client qui veut « du texte plus petit, c'est plus élégant ».
1. Longueur de ligne : 60 à 75 caractères
Au-delà de ~75 caractères par ligne, l'œil peine à retrouver le début de la ligne suivante. En dessous de ~45, le rythme de lecture est haché par les retours à la ligne trop fréquents.
/* La façon la plus simple de respecter ça */
.l-prose {
max-width: 65ch; /* 65 caractères, littéralement */
}
Conséquence sur ta nomenclature : un l-container à 1200px, c'est bien pour une grille de cartes. C'est beaucoup trop large pour un article. D'où l'intérêt d'un layout dédié au texte long.
2. Zone tactile : 44 × 44 px minimum
C'est la taille moyenne de la pulpe d'un doigt. En dessous, le taux d'erreur de tap grimpe en flèche. Un bouton de 30px de haut sur mobile est un bouton qu'on rate.
.c-btn-icon {
width: 44px;
height: 44px; /* la zone tactile */
display: flex;
align-items: center;
justify-content: center;
}
.c-btn-icon svg {
width: 20px; /* l'icône visible */
}
3. Contraste : 4.5:1 minimum
| Contexte | Ratio minimum | Norme |
|---|---|---|
| Texte courant (moins de 18px) | 4.5 : 1 | WCAG AA |
| Grand texte (18px+ gras, ou 24px+) | 3 : 1 | WCAG AA |
| Éléments d'interface (bordures de champs, icônes) | 3 : 1 | WCAG AA |
| Idéal, confort maximum | 7 : 1 | WCAG AAA |
Conséquence sur tes tokens : le gris clair « élégant » à #aaa sur fond blanc donne un ratio de 2.3:1. Il est illisible pour une partie de tes utilisateurs. Un token --color-text-muted doit être testé, pas choisi à l'œil.
4. La hiérarchie visuelle a besoin d'écarts francs
Un H2 à 20px et un corps à 18px : l'œil ne perçoit pas la différence. La hiérarchie n'existe pas.
5. La loi de proximité (Gestalt)
Deux éléments proches sont perçus comme liés. Deux éléments éloignés, comme séparés. C'est automatique et pré-conscient.
La conséquence, terriblement fréquente : un titre de section qui a autant d'espace au-dessus qu'en dessous. L'œil ne sait plus s'il appartient au bloc précédent ou au suivant.
Titre avec 32px au-dessus et 32px en dessous. L'œil hésite : ce titre annonce quoi ?
64px au-dessus, 16px en dessous. Le titre « colle » visuellement à ce qu'il introduit. Aucune ambiguïté.
Conséquence sur ta nomenclature : ton échelle d'espacement doit avoir assez de crans pour exprimer cette asymétrie. C'est aussi pourquoi le gap uniforme d'un l-stack ne suffit pas toujours — parfois, un titre a besoin de sa propre relation d'espacement avec ce qui suit.
6. La cohérence bat l'optimalité
C'est la justification profonde de tout ce document : la nomenclature et les échelles ne servent pas à trouver la valeur parfaite. Elles servent à garantir que tu réutilises la même valeur.
Cycle de vie
Un design system n'est pas figé. Sans règles d'entretien, il pourrit en 18 mois.
1. Le symptôme d'un système qui pourrit
- Des classes que personne n'ose supprimer « au cas où »
- Des noms qui ne veulent plus rien dire (
.c-card-new, deux ans plus tard) - Deux classes qui font la même chose avec des noms différents
- Des
!importantqui s'accumulent pour contourner l'existant - Un nouveau venu qui préfère créer une classe plutôt que chercher si elle existe
2. Auditer : les classes orphelines
Le Class Manager d'Elementor v4 affiche le nombre d'utilisations de chaque classe — et où. Sers-t'en.
| Nombre d'usages | Diagnostic | Action |
|---|---|---|
| 0 | Orpheline — vestige d'une idée abandonnée | Supprime, sans état d'âme |
| 1 | Ne mérite pas d'être globale (règle de trois) | Remets le style en local, supprime la classe |
| 2 | Zone grise — surveille | Garde si une 3e occurrence est prévisible |
| 3+ | Le système fonctionne | Rien à faire |
Rythme recommandé : un audit à chaque fin de projet, avant la livraison. C'est le seul moment où tu as encore tout le contexte en tête.
3. Renommer une classe utilisée 40 fois
Ça arrive : tu as nommé .c-btn-blue au début du projet, tu as compris ton erreur. Comment corriger sans tout casser ?
.c-btn--primary existe désormais à côté de .c-btn-blue. Les deux ont le même style. Rien n'est cassé.
À chaque page que tu touches pour une autre raison, remplace l'ancienne classe par la nouvelle. Pas de « grand soir » de refactoring.
Le Class Manager te dit combien d'occurrences de l'ancien nom subsistent. Quand il tombe à zéro…
Le compteur à 0 est ta garantie que rien ne casse. C'est exactement à ça que sert le Usage Count.
4. Déprécier plutôt que supprimer brutalement
Sur un projet livré à un client qui édite lui-même ses pages, une suppression brutale peut casser une page que tu n'as pas vue. Marque d'abord, supprime ensuite :
/* DÉPRÉCIÉ — remplacé par .c-btn--primary
À supprimer après migration complète (audit prévu : mars) */
.c-btn-blue { ... }
5. La règle du « ajouter coûte plus cher que réutiliser »
6. Le kit qui voyage entre projets
C'est le cœur de la reproductibilité : ta nomenclature (c-btn, c-card, l-section, l'échelle d'espacement) reste identique d'un client à l'autre. Seules les valeurs changent — la couleur primaire, la police, éventuellement le rythme d'espacement.
Elementor v4 permet d'exporter et réimporter une collection complète de classes et variables. Concrètement, ton point de départ sur un nouveau projet n'est jamais une page blanche : c'est ton kit, dont tu échanges les valeurs de marque.
Dans Elementor v4
Où poser chaque brique, concrètement, dans l'interface.
Le bon outil pour chaque décision
| Ce que tu veux définir | Où, dans Elementor v4 | Surtout pas |
|---|---|---|
| Couleurs de marque, échelle d'espacement, tailles | Variables Manager | Des valeurs en dur dans chaque widget |
| Hiérarchie de titres H1→H6 | Réglages du site → Typographie | Une classe .u-text-4xl posée à la main |
| Espacement entre les enfants d'un container | Réglage Gap natif du Container | Une marge sur chaque enfant |
| Un motif visuel répété 3+ fois | Class Manager — classe globale | Copier-coller le style sur chaque instance |
| Un style utilisé une seule fois | Style local de l'élément | Une classe globale (dette inutile) |
| Un bloc structurel complet réutilisé | Component (v4) — synchronisé globalement | Dupliquer la section à la main |
La règle qui évite le chaos
u-gap-4, tu ne sauras plus jamais d'où il vient. Choisis, écris-le dans ta doc de projet, et tiens-toi-y.Pourquoi le CSS-first change tout
En v4, le style d'un élément est porté par des classes globales réutilisables plutôt que par des réglages isolés sur chaque widget. Conséquences directes :
- Moins de styles inline → pages plus légères, meilleure performance
- Une modification se propage → tu changes la classe, tout le site suit
- Le Class Manager sait où chaque classe est utilisée → tu ne modifies plus à l'aveugle
Containers Flexbox : le socle
Les Containers génèrent un DOM nettement plus léger que l'ancien système Section → Column (2 niveaux au lieu de 4), et exposent nativement le gap. Pour tout nouveau projet : Containers, sans hésiter.
Checklist de démarrage projet
- Importe ton kit de classes et variables du projet précédent
- Change uniquement les valeurs de marque (couleurs, police) dans le Variables Manager
- Définis la hiérarchie typographique dans les Réglages du site
- Construis avec les Containers, en utilisant le gap natif
- Ne crée une nouvelle classe que si un motif se répète 3 fois
- Avant livraison : audite les orphelines dans le Class Manager
Et Figma dans tout ça ?
Une fois cette nomenclature stable côté Elementor, le passage à Figma est presque mécanique :
- Tes variables Figma portent les mêmes noms que tes variables Elementor
- Tes composants Figma portent les mêmes noms que tes classes
c- - Ton auto-layout Figma utilise les mêmes crans d'espacement
clamp() n'a pas d'équivalent natif dans Figma. Une valeur fluide (« 32px sur mobile, 56px sur desktop, en continu ») ne peut être représentée — seulement annotée. C'est la frontière irréductible entre les deux outils, et la raison pour laquelle la maquette Figma ne sera jamais la source de vérité absolue du responsive.Glossaire
Tout le vocabulaire de ce document, en français simple.
Nomenclature
| Composant (c-) | Objet autonome et réutilisable. Gère son intérieur, jamais sa position. |
| Layout (l-) | Structure qui positionne les composants. Gère l'espace, pas l'apparence. |
| Utilitaire (u-) | Une classe, une propriété. Au-delà de 3 empilés, c'est un composant qui s'ignore. |
| État (is- / has-) | Condition temporaire, posée par le JavaScript. is-open, has-error. |
| BEM | Bloc / Élément / Modificateur. .c-card__title--big. Toujours plat, jamais chaîné. |
| Variation (--) | Une sorte permanente de composant. À distinguer d'un état. |
| Règle de trois | On ne nomme qu'à la 3e occurrence. Avant, c'est peut-être une coïncidence. |
CSS
| Padding | Espace intérieur, entre le contenu et le bord. |
| Margin | Espace extérieur, entre deux éléments. L'exception, pas la règle. |
| Gap | Espace entre les enfants d'un flex/grid, posé sur le parent. Le réflexe par défaut. |
| Cascade | Le mécanisme qui arbitre entre deux règles contradictoires. |
| Spécificité | Le « poids » d'un sélecteur. Un ID bat toutes les classes. À garder basse et plate. |
| !important | Un symptôme d'architecture cassée, jamais une solution. |
| clamp() | Fait varier une valeur en continu entre deux tailles d'écran. Sans media query. |
| Échelle | Série de valeurs fixes. Sa fonction n'est pas d'être parfaite, mais d'être réutilisée. |
| ch | Unité = largeur du caractère « 0 ». max-width: 65ch = ligne idéale de lecture. |
Elementor v4
| Variables Manager | Panneau centralisant couleurs, tailles, espacements. Ta source de vérité. |
| Class Manager | Panneau des classes globales. Affiche où et combien de fois chacune est utilisée. |
| Classe globale | Réutilisable sur tout le site. Une modification se propage partout. |
| Classe locale | Générée automatiquement quand tu stylises un widget directement. Écrase la classe globale. |
| Container | Le Flexbox Container. C'est ton layout natif — utilise son gap. |
| Component | Un bloc structurel réutilisable et synchronisé globalement. |
UX
| 65ch | Longueur de ligne idéale pour la lecture. Au-delà de 75, l'œil se perd. |
| 44 × 44px | Zone tactile minimum sur mobile. Le padding compte, pas la taille de l'icône. |
| 4.5:1 | Contraste minimum pour du texte courant (WCAG AA). |
| Loi de proximité | Deux éléments proches sont perçus comme liés. D'où l'espacement asymétrique des titres. |