frontend / css
Architecture CSS : BEM, utility-first, cascade layers
Explication
Ce que vous allez apprendre
- Comprendre pourquoi le CSS devient difficile à maintenir à grande échelle sans convention
- Structurer des classes avec BEM (
bloc__element--modificateur) pour neutraliser les conflits de spécificité - Utiliser l'approche utility-first (façon Tailwind) et connaître son compromis
- Définir un ordre de priorité explicite entre groupes de règles avec
@layer - Intégrer une librairie CSS tierce sans craindre qu'elle n'écrase vos propres styles
Dans quel contexte ?
Une entreprise de dix développeurs front-end travaille sur la même application depuis deux ans. Le fichier de styles dépasse 15 000 lignes, et chaque modification d'une classe existante risque de casser un composant ailleurs dans l'application. C'est exactement le genre de situation où BEM, l'utility-first ou les cascade layers deviennent indispensables plutôt que de simples préférences stylistiques.
Étape 1 : un problème d'échelle, pas de petit projet
Sur un petit projet de quelques pages, écrire du CSS "au fil de l'eau" fonctionne très bien. Le problème apparaît à plus grande échelle, sur des milliers de lignes et plusieurs développeurs.
Étape 2 : ce qui se dégrade concrètement
Les conflits de spécificité, vus dans la première leçon de ce cours, se multiplient, et plus personne n'ose modifier un style existant de peur de casser autre chose ailleurs. Voici trois façons différentes de structurer le CSS pour rester maintenable.
Étape 3 : BEM, neutraliser la spécificité par convention
Commençons par BEM (Block, Element, Modifier), qui nomme toutes les classes selon un schéma strict (bloc__element--modificateur) et n'utilise QUE des sélecteurs de classe simples, jamais imbriqués.
Étape 4 : pourquoi cette convention fonctionne
Résultat : toutes les classes BEM ont exactement la même spécificité, éliminant structurellement tout risque de conflit d'ordre imprévisible. Le prix à payer est un nommage plus verbeux.
Étape 5 : l'approche inverse, utility-first
L'utility-first, popularisée par Tailwind CSS, prend le problème à l'envers : plutôt que des classes sémantiques personnalisées, on compose le style directement dans le markup avec de petites classes atomiques comme px-4 ou bg-blue-600.
Étape 6 : un troisième outil pour un problème différent
Ni BEM ni l'utility-first ne règlent l'ordre de priorité entre plusieurs sources de CSS. @layer répond justement à ce problème en définissant un ORDRE DE PRIORITÉ EXPLICITE entre groupes de règles, indépendant de la spécificité ou de l'ordre d'écriture.
| Approche | Force | Faiblesse |
|---|---|---|
| BEM | zéro ambiguïté de spécificité | verbeux à l'écriture |
| Utility-first | rapidité, pas de nommage | markup chargé |
| Cascade layers | ordre explicite, orthogonal à la spécificité | nécessite une planification d'architecture |
Prérequis
Cette leçon suppose que vous êtes déjà à l'aise avec la notion de spécificité (nombre de classes, d'ID, d'éléments dans un sélecteur), abordée en tout début de ce cours. Si ce n'est pas encore clair, un rapide retour en arrière évitera bien des confusions ici.
Et ensuite ?
C'est particulièrement précieux pour intégrer une librairie CSS tierce sans craindre qu'elle n'écrase vos propres styles. Une fois ces approches comprises, la prochaine étape explore des sélecteurs récents qui remplacent souvent du JavaScript.
Commandes & code
Architecture CSS : BEM, utility-first, cascade layers
Organiser des milliers de lignes de CSS pour qu'elles restent prévisibles à grande échelle.
/* BEM : Block__Element--Modifier -- élimine l'ambiguïté de spécificité par convention */
.carte { } /* Block */
.carte__titre { } /* Element : appartient au block */
.carte__image { }
.carte--mise-en-avant { } /* Modifier : variante du block */
.carte__titre--large { } /* Modifier d'un element */
/* Toutes les classes BEM ont volontairement la MÊME spécificité (0,0,1,0) :
jamais de sélecteurs imbriqués type .carte .titre, jamais de conflit d'ordre */<!-- Utility-first (façon Tailwind) : composition de classes atomiques -->
<button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">
Envoyer
</button>
<!-- avantage : pas de CSS à nommer/maintenir, tout est visible dans le markup
inconvénient : markup verbeux, répétition entre composants similaires -->/* Cascade layers (@layer) : contrôle explicite de l'ordre de priorité,
INDÉPENDAMMENT de la spécificité ou de l'ordre d'écriture */
@layer reset, base, composants, utilitaires;
/* déclare l'ordre : reset < base < composants < utilitaires
(peu importe où chaque layer est réellement défini dans les fichiers) */
@layer reset {
* { margin: 0; padding: 0; box-sizing: border-box; }
}
@layer base {
body { font-family: system-ui; line-height: 1.5; }
a { color: var(--couleur-primaire); }
}
@layer composants {
.bouton { padding: 8px 16px; border-radius: 8px; }
}
@layer utilitaires {
.text-center { text-align: center; }
/* une simple classe utilitaire, même à spécificité (0,0,1,0),
bat désormais TOUJOURS .bouton grâce à l'ordre du layer,
peu importe qui est chargé/écrit en dernier */
}
/* Le CSS hors de tout @layer a toujours la priorité la plus haute */
.override-ponctuel { color: red !important; } /* reste au-dessus de tous les layers *//* @import avec layer : intégrer une librairie tierce sans risque de conflit */
@import url("normalize.css") layer(reset);
@import url("composants-tiers.css") layer(composants);| Approche | Force | Faiblesse |
|---|---|---|
| BEM | zéro ambiguïté de spécificité | verbeux à l'écriture |
| Utility-first | rapidité, pas de nommage | markup chargé |
| Cascade layers | ordre explicite, orthogonal à la spécificité | nécessite une planification d'architecture |
Résumé
- BEM neutralise les conflits de spécificité en gardant toutes les classes au même niveau.
- L'utility-first déplace la complexité du CSS vers la composition de classes dans le markup.
@layerdéfinit un ordre de priorité EXPLICITE, indépendant de l'ordre d'écriture ou de la spécificité.- Le CSS hors layer bat toujours le CSS en layer : pratique pour des overrides ponctuels assumés.
Exercices pratiques
Mission : une librairie tierce qui écrase les boutons maison
Objectif : Résoudre un conflit d'intégration de librairie tierce avec les cascade layers et évaluer un choix BEM.
Contexte
Sur un projet de dix développeurs, .bouton (défini par l'équipe, padding: 8px 16px; border-radius: 8px;) est cassé dès qu'une librairie de composants tierce composants-tiers.css est importée après lui : elle définit aussi une règle .bouton avec des valeurs différentes, chargée plus tard dans le build, donc elle gagne systématiquement à spécificité égale. L'équipe ne veut ni renommer sa classe, ni ajouter !important partout.