Retour au cours

frontend / css

Architecture CSS : BEM, utility-first, cascade layers

Leçon 151 exercice

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.

ApprocheForceFaiblesse
BEMzéro ambiguïté de spécificitéverbeux à l'écriture
Utility-firstrapidité, pas de nommagemarkup chargé
Cascade layersordre 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.

css
/* 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 */
html
<!-- 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 -->
css
/* 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 */
css
/* @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);
ApprocheForceFaiblesse
BEMzéro ambiguïté de spécificitéverbeux à l'écriture
Utility-firstrapidité, pas de nommagemarkup chargé
Cascade layersordre 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.
  • @layer dé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

1 disponible
1

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.

Résoudre l’exercice →