Pourquoi la cascade devient vite difficile à maîtriser
La cascade est l’un des mécanismes les plus puissants de CSS, mais aussi l’un des plus mal compris. Quand un projet grandit, les styles viennent souvent de plusieurs sources : un reset CSS, une librairie de composants, un design system interne, des feuilles de style globales, des modules CSS, des composants React ou Vue, parfois même du style injecté par des dépendances.
Le résultat est classique : on ajoute une classe, elle ne s’applique pas. On augmente la spécificité. Puis on ajoute un sélecteur plus long. Puis, dans l’urgence, un !important. Petit à petit, le CSS devient une bataille de priorité plutôt qu’un système prévisible.
Les cascade layers, introduits avec @layer, apportent une réponse élégante à ce problème. Ils permettent de déclarer explicitement des couches de styles, avec un ordre de priorité contrôlé. Ce n’est pas un remplacement de la spécificité, mais une couche d’organisation au-dessus d’elle.
Si vous avez déjà structuré vos composants avec des approches modernes comme les container queries CSS, les cascade layers complètent très bien cette logique : les composants deviennent plus autonomes, mais l’ordre global des styles reste maîtrisé.
Le principe de @layer
Un layer CSS est une couche nommée dans laquelle vous pouvez placer des règles. La subtilité importante est la suivante : l’ordre des layers compte plus que la spécificité entre layers différents.
Exemple simple :
@layer reset, base, components, utilities;
@layer reset {
button {
all: unset;
}
}
@layer components {
.button {
padding: 0.75rem 1rem;
border-radius: 0.5rem;
background: black;
color: white;
}
}
@layer utilities {
.bg-red {
background: red;
}
}
Dans cet exemple, les règles du layer utilities sont prioritaires sur celles du layer components, car utilities est déclaré après components dans la liste initiale :
@layer reset, base, components, utilities;
Cela signifie qu’une simple classe utilitaire peut surcharger une règle de composant, même si le sélecteur du composant semble plus spécifique dans certains cas. On ne dépend plus uniquement d’une escalade de sélecteurs.
Sans layer : le piège de la spécificité
Imaginons une carte produit stylée de manière classique :
.card .button.primary {
background: black;
color: white;
}
.bg-red {
background: red;
}
Avec ce HTML :
<article class="card">
<button class="button primary bg-red">Acheter</button>
</article>
Vous pourriez vous attendre à ce que .bg-red rende le bouton rouge. Pourtant, .card .button.primary a une spécificité plus forte que .bg-red. La règle utilitaire ne gagne donc pas.
On voit alors apparaître des solutions fragiles :
.card .button.primary.bg-red {
background: red;
}
Ou pire :
.bg-red {
background: red !important;
}
Ces solutions fonctionnent ponctuellement, mais elles augmentent la dette technique. Avec les cascade layers, on peut exprimer l’intention plus proprement.
Avec layers : une priorité explicite
Voici une version plus robuste :
@layer components, utilities;
@layer components {
.card .button.primary {
background: black;
color: white;
}
}
@layer utilities {
.bg-red {
background: red;
}
}
La classe .bg-red gagne parce que le layer utilities est prioritaire sur le layer components. Ce n’est pas une astuce : c’est une décision d’architecture.
On peut maintenant formuler une règle claire pour l’équipe : les styles de composants définissent l’apparence par défaut, les utilitaires servent aux ajustements ponctuels et sont autorisés à surcharger les composants.
Un ordre recommandé pour un projet frontend
Il n’existe pas d’ordre universel, mais une structure fréquente ressemble à ceci :
@layer reset, tokens, base, layout, components, utilities, overrides;
Chaque couche a un rôle précis.
reset
Le layer reset contient les styles de normalisation : marges par défaut, comportement des éléments interactifs, box model, images fluides.
@layer reset {
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
}
img,
svg,
video {
max-width: 100%;
display: block;
}
}
tokens
Le layer tokens contient les variables CSS du design system : couleurs, espacements, typographie, rayons, ombres.
@layer tokens {
:root {
--color-background: #ffffff;
--color-text: #111111;
--space-2: 0.5rem;
--space-4: 1rem;
--radius-md: 0.5rem;
}
}
base
Le layer base définit les styles généraux des éléments HTML.
@layer base {
body {
font-family: system-ui, sans-serif;
background: var(--color-background);
color: var(--color-text);
line-height: 1.5;
}
a {
color: currentColor;
text-underline-offset: 0.2em;
}
}
layout
Le layer layout regroupe les structures réutilisables : grille, conteneurs, sections, espacements de page.
@layer layout {
.container {
width: min(100% - 2rem, 72rem);
margin-inline: auto;
}
.stack {
display: grid;
gap: var(--space-4);
}
}
components
Le layer components contient les blocs UI : boutons, cartes, menus, formulaires, modales.
@layer components {
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: 0.75rem 1rem;
border-radius: var(--radius-md);
background: var(--color-text);
color: var(--color-background);
font-weight: 600;
}
}
utilities
Le layer utilities contient les classes atomiques ou très ciblées.
@layer utilities {
.text-center {
text-align: center;
}
.hidden {
display: none;
}
.mt-4 {
margin-top: var(--space-4);
}
}
overrides
Le layer overrides doit rester exceptionnel. Il sert aux cas d’intégration, de migration ou de surcharge ponctuelle difficile à placer ailleurs.
@layer overrides {
.legacy-widget button {
font: inherit;
}
}
Si ce layer grossit trop, c’est souvent le signe qu’un composant ou une règle de base doit être repensé.
Utiliser @layer avec des imports
Les cascade layers sont particulièrement utiles avec des fichiers séparés. Vous pouvez placer un import entier dans un layer :
@layer reset, tokens, base, layout, components, utilities;
@import url("./reset.css") layer(reset);
@import url("./tokens.css") layer(tokens);
@import url("./base.css") layer(base);
@import url("./layout.css") layer(layout);
@import url("./components.css") layer(components);
@import url("./utilities.css") layer(utilities);
Cette approche est très pratique dans une architecture frontend moderne, car elle rend l’ordre global lisible dès le point d’entrée CSS. Elle évite aussi de dépendre uniquement de l’ordre physique des imports, qui devient rapidement confus dans de gros projets.
Dans une application Next, Nuxt, React ou Vue, vous pouvez garder ce fichier comme point d’entrée global, puis laisser les composants gérer leurs styles locaux quand c’est pertinent. L’important est d’avoir une convention claire pour les styles partagés.
Attention aux styles hors layer
Un point souvent oublié : les styles non placés dans un layer sont prioritaires sur les styles placés dans des layers.
@layer components {
.button {
background: black;
}
}
.button {
background: blue;
}
Ici, le bouton sera bleu. La règle non-layerisée gagne.
Cela peut surprendre, mais c’est aussi utile pour migrer progressivement. Vous pouvez introduire @layer sans devoir déplacer tout le CSS immédiatement. En revanche, à long terme, mieux vaut éviter d’avoir trop de règles globales hors layer, sinon vous perdez une partie du bénéfice d’organisation.
Cascade layers et !important
Les cascade layers ne suppriment pas totalement le besoin de !important, mais ils en réduisent fortement l’usage. Dans une base CSS saine, !important devrait rester réservé à quelques cas spécifiques : classes utilitaires très intentionnelles, règles d’accessibilité, ou surcharges de styles tiers impossibles à contrôler autrement.
Avant d’écrire !important, posez-vous trois questions :
- La règle est-elle dans le bon layer ?
- L’ordre des layers reflète-t-il bien l’intention du projet ?
- Le sélecteur est-il inutilement spécifique ?
Dans beaucoup de cas, déplacer la règle dans un layer plus approprié suffit.
Exemple avec un composant React
Voici un composant React simple :
type AlertProps = {
type?: "info" | "success" | "error";
children: React.ReactNode;
};
export function Alert({ type = "info", children }: AlertProps) {
return <div className={`alert alert-${type}`}>{children}</div>;
}
Et son CSS :
@layer components {
.alert {
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid transparent;
}
.alert-info {
background: #eff6ff;
border-color: #bfdbfe;
}
.alert-success {
background: #f0fdf4;
border-color: #bbf7d0;
}
.alert-error {
background: #fef2f2;
border-color: #fecaca;
}
}
Si vous avez besoin d’une marge ou d’un alignement particulier dans une page, vous pouvez passer par un layer utilitaire :
@layer utilities {
.mt-4 {
margin-top: 1rem;
}
.max-w-md {
max-width: 28rem;
}
}
Puis côté JSX :
<div className="max-w-md">
<Alert type="success">Votre profil a bien été enregistré.</Alert>
</div>
Le composant garde sa responsabilité : afficher une alerte. La page gère le contexte de mise en page. Cette séparation rejoint les mêmes préoccupations que l’on rencontre avec le typage des données et la centralisation des responsabilités dans un projet frontend, comme dans l’article sur le typage des données API en TypeScript.
Bonnes pratiques pour adopter les cascade layers
Commencez par déclarer l’ordre des layers dans un seul fichier d’entrée. Cette déclaration doit être stable, documentée et connue de l’équipe.
Évitez de créer trop de layers. Trois à sept couches suffisent dans la majorité des projets. Si chaque fonctionnalité a son propre layer, vous risquez de déplacer la complexité au lieu de la réduire.
Gardez les sélecteurs simples. @layer n’est pas une excuse pour écrire des sélecteurs très profonds. Une bonne architecture CSS combine des layers clairs, des classes explicites et une spécificité raisonnable.
Enfin, migrez progressivement. Vous pouvez commencer par placer votre reset, vos tokens et vos utilitaires dans des layers, puis déplacer les composants au fil de l’eau. Cette adoption incrémentale rend la technique accessible même sur un projet existant.
Conclusion
Les cascade layers apportent à CSS ce qui lui a longtemps manqué dans les gros projets : une manière native de déclarer des priorités d’architecture. Au lieu de laisser la spécificité décider seule, vous pouvez exprimer clairement l’ordre entre reset, base, layout, composants et utilitaires.
Ce mécanisme ne remplace pas les bonnes pratiques CSS, mais il les rend plus faciles à appliquer. Pour un design system, une application frontend ou un site maintenu sur plusieurs années, @layer est un outil simple, robuste et très rentable à maîtriser.