Skip to main content

Chapitre 35 : Architecture des couches avec @layer

Gestion de la priorité de la cascade via Cascade Layers

Pendant des décennies, les développeurs CSS ont lutté contre la "guerre des sélecteurs". Pour forcer un style à s'appliquer, on augmentait la spécificité en ajoutant des classes, des IDs, ou pire, en abusant de !important. L'introduction de la règle @layer change radicalement la donne en permettant de définir l'ordre de priorité des styles indépendamment de la spécificité des sélecteurs.

Le concept de Cascade Layers

1. Quoi

Les Cascade Layers (couches de cascade) sont une fonctionnalité CSS qui permet de regrouper des blocs de styles dans des "couches" nommées. L'innovation majeure réside dans le fait que l'ordre de déclaration des couches prime sur la spécificité des sélecteurs.

Si un style est défini dans une couche de priorité inférieure, même un sélecteur extrêmement spécifique (comme un ID) ne pourra pas écraser un style défini dans une couche de priorité supérieure, même si ce dernier utilise un sélecteur très simple (comme une seule classe).

2. Pourquoi

Dans un projet professionnel, on mélange souvent plusieurs sources de CSS :

  • Un Reset ou un Normalize (base globale).
  • Un Framework CSS (Bootstrap, Tailwind, etc.).
  • Des Styles composants (Design System interne).
  • Des Overwrites (ajustements spécifiques à une page).

Sans @layer, si le framework utilise un sélecteur très spécifique, vous devez écrire un sélecteur encore plus spécifique pour le modifier. Avec @layer, vous placez le framework dans une couche framework et vos modifications dans une couche app. Puisque app est déclarée après framework, vos styles gagneront toujours, peu importe la complexité du sélecteur du framework.

3. Comment

A. Syntaxe de base

On peut déclarer les couches de deux manières : via l'ordre de déclaration initial ou via la règle @layer.

/* 1. Définition de l'ordre de priorité (du moins prioritaire au plus prioritaire) */
@layer reset, framework, components, utilities;

/* 2. Assignation des styles aux couches */
@layer reset {
body { margin: 0; padding: 0; }
}

@layer framework {
.btn { padding: 1rem; background: blue; }
}

@layer components {
/* Ce style gagnera sur .btn du framework, même si le sélecteur est simple */
.btn { background: red; }
}

B. Cas concret : Architecture d'entreprise

Voici comment structurer un projet réel pour garantir que les Design Tokens et les overrides soient respectés sans conflit de spécificité.

/* 
Définition explicite de la hiérarchie.
L'ordre ici détermine la priorité :
base < theme < components < overrides
*/
@layer base, theme, components, overrides;

@layer base {
/* Reset global et typographies de base */
html {
box-sizing: border-box;
font-family: 'Inter', sans-serif;
}

h1, h2, h3 {
margin-bottom: 1rem;
line-height: 1.2;
}
}

@layer theme {
/* Application des Design Tokens */
:root {
--primary-color: #3490dc;
--secondary-color: #ffed4a;
}

body {
background-color: var(--primary-color);
color: white;
}
}

@layer components {
/* Styles de composants réutilisables */
.card {
background: white;
color: #333;
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}

/* Même si on utilise un sélecteur complexe ici,
il reste soumis à la priorité de la couche 'components' */
body .main-content .card {
border: 2px solid black;
}
}

@layer overrides {
/* Ajustements ponctuels pour des pages spécifiques */
/* Ce style gagnera TOUJOURS sur .card de la couche components */
.card {
border: 5px solid gold;
}
}

/*
IMPORTANT : Les styles non assignés à une couche (Unlayered)
ont la priorité maximale sur TOUTES les couches.
*/
.emergency-fix {
display: none !important;
}

C. Limitations

  1. L'effet inverse de !important : C'est le piège le plus dangereux. Lorsqu'un style est marqué !important, la logique de priorité des couches est inversée. Un !important dans une couche de priorité basse gagnera sur un !important dans une couche de priorité haute.
  2. L'ordre de déclaration : Si vous ne définissez pas l'ordre au début avec @layer layer1, layer2;, CSS utilisera l'ordre d'apparition dans le fichier. Cela peut devenir chaotique dans des architectures de fichiers multiples.
  3. Compatibilité : Bien que supporté par tous les navigateurs modernes, les très vieux navigateurs ignoreront simplement les blocs @layer.

4. Zone de Danger

Erreur commune : Utiliser @layer pour remplacer totalement la spécificité. Pourquoi ? La spécificité fonctionne toujours à l'intérieur d'une même couche. Si vous avez deux règles dans la couche components, c'est la spécificité classique qui tranchera.

Bonne pratique : Utiliser @layer pour organiser les "domaines" de styles et réserver la spécificité pour les variations internes à un composant.

Hiérarchie de priorité des Cascade Layers

Questions clés

1. Quelle est la différence fondamentale entre la spécificité et les Cascade Layers ?

Découvrir la réponse

La spécificité calcule le "poids" d'un sélecteur (ID > Classe > Élément). Les Cascade Layers créent des groupes de priorité. Un style dans une couche supérieure gagne toujours sur un style dans une couche inférieure, même si le sélecteur de ce dernier est beaucoup plus spécifique.

2. Que se passe-t-il si je déclare un style en dehors de toute couche ?

Découvrir la réponse

Les styles "unlayered" sont considérés comme ayant la priorité la plus élevée de toutes les couches. Ils écraseront n'importe quelle règle définie dans une @layer, quelle que soit la spécificité.

3. Comment gérer l'importation de fichiers CSS externes dans une couche ?

Découvrir la réponse

On peut utiliser la syntaxe @import avec la clause @layer : @import "framework.css" layer(framework); Cela permet d'encapsuler un framework tiers entier dans une couche sans modifier son code source.

4. Pourquoi !important se comporte-t-il différemment dans les couches ?

Découvrir la réponse

C'est une décision de conception pour permettre aux couches de base (comme un reset) de pouvoir "forcer" certains comportements si nécessaire, tout en laissant les couches supérieures gérer le design. En résumé : pour les styles normaux, la dernière couche gagne. Pour les styles !important, la première couche gagne.

Mise en pratique

Exercice 1 : Reproduction guidée

Objectif : Créer une structure simple avec trois couches : base, theme, et utils.

  • La couche base doit mettre le fond de page en gris clair.
  • La couche theme doit mettre le fond de page en bleu.
  • La couche utils doit mettre le fond de page en vert.
  • Vérifiez que le fond est bien vert.
Découvrir la solution commentée
/* On définit l'ordre de priorité dès le début */
@layer base, theme, utils;

@layer base {
body {
background-color: lightgrey;
}
}

@layer theme {
body {
background-color: blue;
}
}

@layer utils {
body {
background-color: green;
}
}

Exercice 2 : Adaptation (Le combat de la spécificité)

Objectif : Prouver que la couche gagne sur la spécificité.

  1. Créez une couche framework et une couche app.
  2. Dans framework, créez une règle avec deux éléments pour un bouton : body .btn { color: red; }.
  3. Dans app, créez une règle simple : .btn { color: white; }.
  4. Assurez-vous que la couleur du texte dans le bouton est blanche.
Découvrir la solution commentée
/* L'ordre est crucial : app est après framework, donc plus prioritaire */
@layer framework, app;

@layer framework {
/* Sélecteur avec deux éléments (poids plus élevé que .btn seul) */
body .btn {
color: red;
}
}

@layer app {
/* Sélecteur simple (poids faible) */
/* Mais comme il est dans une couche supérieure, il gagne */
.btn {
color: white;
}
}

Exercice 3 : Conception (Architecture de Design System)

Objectif : Mettre en place une architecture robuste pour un site e-commerce et observer comment la priorité des couches prime sur la spécificité.

HTML de départ (index.html) :

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="container">
<div class="card">
<h2>Casque Bluetooth Pro</h2>
<p>Son exceptionnel avec réduction de bruit active.</p>
<button class="btn">Ajouter au panier</button>
</div>
<div class="card">
<h2>Souris Gaming RGB</h2>
<p>Capteur 25 600 DPI, RGB personnalisable.</p>
<button class="btn">Ajouter au panier</button>
</div>
</div>
</body>
</html>

Contenu attendu dans chaque fichier :

FichierRôleCe qu'il doit contenir
reset.cssMoins prioritaireReset global + fond page lightgrey
grid.cssPriorité moyenne.container en Grid auto-responsive + .btn { color: gray } (sera écrasé par buttons.css)
cards.cssPriorité haute.card avec fond blanc, bordure fine 1px, padding, border-radius
buttons.cssPriorité haute.btn fond bleu, texte blanc, padding, border-radius
hotfixes.cssPriorité maximale (hors couche).card { border: 3px solid red; } (doit écraser la bordure de cards.css)

Consignes :

  1. Créez les 5 fichiers avec le contenu décrit dans le tableau ci-dessus.
  2. Créez main.css qui orchestre les imports avec la hiérarchie resetgridcomponents.
  3. Importez hotfixes.css sans aucune couche pour lui donner la priorité maximale.
Découvrir la solution commentée

reset.css

*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: sans-serif;
background-color: lightgrey;
padding: 2rem;
}

grid.css

.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 1rem;
}

/*
Ce style sera écrasé par buttons.css (même couche 'components'
mais déclaré après), puis par hotfixes si besoin.
*/
.btn {
color: gray;
}

cards.css

.card {
background: white;
border: 1px solid #ddd; /* Sera écrasé par hotfixes.css */
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 0.75rem;
}

buttons.css

/* 
Même couche 'components' que cards.css, mais importé après.
Gagne donc sur .btn { color: gray } de grid.css (couche inférieure).
*/
.btn {
background-color: #007bff;
color: white;
padding: 0.5rem 1.25rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
align-self: flex-start;
}

hotfixes.css

/* 
Correctif d'urgence : unlayered, priorité absolue sur toutes les couches.
Même un sélecteur très spécifique dans 'components' ne peut pas gagner.
*/
.card {
border: 3px solid red;
}

main.css

/* 
1. On définit l'ordre des couches.
reset < grid < components (du moins au plus prioritaire)
*/
@layer reset, grid, components;

/* 2. Importation des fichiers dans leurs couches */
@import "reset.css" layer(reset);
@import "grid.css" layer(grid);
@import "cards.css" layer(components);
@import "buttons.css" layer(components);

/*
3. hotfixes.css importé sans couche → "unlayered" → priorité maximale.
*/
@import "hotfixes.css";

Ce que vous devriez observer :

  • La bordure des .card est rouge et épaisse (hotfixes.css, unlayered) et non fine (cards.css, couche components).
  • Le texte du .btn est blanc (buttons.css, couche components) et non gris (grid.css, couche grid moins prioritaire).
  • Le fond de page reste gris clair (reset.css) car aucune couche supérieure ne le redéfinit.