Cours HTML et CSS · Module 9: CSS moderne

Cascade Layers - Contrôler l'ordre de la cascade CSS

21 min de lecture
Dans cette leçon4

Tu ajoutes une bibliothèque de composants à ton projet. Ses boutons ont un sélecteur du genre #app .btn.btn-primary, et ta simple classe .button perd face à lui en spécificité. Tu ajoutes !important, l'auteur de la bibliothèque fait de même, et commence alors une guerre où plus personne ne sait pourquoi le bouton a cette couleur. Les CSS Cascade Layers (@layer) sont une fonctionnalité CSS qui te permet de contrôler explicitement l'ordre de la cascade des styles. Elles permettent d'organiser le CSS en couches logiques avec une hiérarchie d'importance définie, ce qui simplifie considérablement la gestion de feuilles de style complexes.

Les bases des Cascade Layers

Que sont les Cascade Layers ?

Les Cascade Layers te permettent de définir des couches CSS nommées, où les styles des couches situées plus haut dans la hiérarchie prennent le dessus sur les styles des couches inférieures, indépendamment de la spécificité des sélecteurs. Voici quatre couches classiques :

1/* Définition d'une couche et de ses styles */
2@layer reset {
3  * {
4    margin: 0;
5    padding: 0;
6    box-sizing: border-box;
7  }
8}
9
10@layer base {
11  body {
12    font-family: 'Inter', sans-serif;
13    line-height: 1.6;
14    color: #333;
15  }
16
17  h1, h2, h3, h4, h5, h6 {
18    margin-bottom: 0.5em;
19    font-weight: 600;
20  }
21}
22
23@layer components {
24  .button {
25    padding: 0.75rem 1.5rem;
26    border: none;
27    border-radius: 6px;
28    background: #007bff;
29    color: white;
30    cursor: pointer;
31  }
32}
33
34@layer utilities {
35  .text-center { text-align: center; }
36  .hidden { display: none; }
37  .mt-4 { margin-top: 1rem; }
38}

Sans déclaration séparée, c'est la première apparition d'un nom qui fixe l'ordre, donc reset se trouve tout en bas et utilities tout en haut. La classe .hidden de utilities l'emportera donc sur n'importe quelle règle de components, même plus spécifique. La spécificité ne compte qu'à l'intérieur d'une même couche. Retiens aussi que les styles ordinaires écrits en dehors des couches l'emportent sur toutes les couches.

Déclarer l'ordre des couches

Tu peux définir explicitement l'ordre des couches au début de la feuille de style, en une seule instruction courte terminée par un point-virgule :

1/* Définition de l'ordre des couches - de la plus basse à la plus haute priorité */
2@layer reset, base, layout, components, utilities;
3
4/* Maintenant tu peux utiliser les couches dans n'importe quel ordre dans le code */
5
6@layer utilities {
7  .text-red { color: red; }
8}
9
10@layer reset {
11  * { margin: 0; }
12}
13
14@layer components {
15  .card {
16    background: white;
17    border: 1px solid #ddd;
18    padding: 1rem;
19  }
20}
21
22/* L'ordre des définitions dans le code n'a pas d'importance - seul l'ordre de la déclaration @layer compte */

L'ordre des blocs dans le code n'a pas d'importance, seul compte l'ordre de la déclaration @layer. Je te recommande de toujours commencer ta feuille de style par une telle ligne, car toute la hiérarchie est alors visible à un seul endroit, comme le plan des étages gravé à l'entrée d'une pyramide.

Techniques avancées des Cascade Layers

1. Couches imbriquées

Les couches peuvent contenir leurs propres couches. On enferme tout le framework dans une seule couche, et à l'intérieur, on organise son reset, sa base, sa mise en page et ses utilitaires :

1@layer framework {
2  /* Couches imbriquées dans la couche framework */
3  @layer reset {
4    *, *::before, *::after {
5      box-sizing: border-box;
6      margin: 0;
7      padding: 0;
8    }
9
10    html {
11      line-height: 1.15;
12      -webkit-text-size-adjust: 100%;
13    }
14  }
15
16  @layer base {
17    body {
18      font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
19      font-size: 1rem;
20      line-height: 1.6;
21      color: #212529;
22      background: #ffffff;
23    }
24
25    img, svg {
26      display: block;
27      max-width: 100%;
28      height: auto;
29    }
30
31    button, input, select, textarea {
32      font: inherit;
33    }
34  }
35
36  @layer layout {
37    .container {
38      width: 100%;
39      max-width: 1200px;
40      margin: 0 auto;
41      padding: 0 1rem;
42    }
43
44    .grid {
45      display: grid;
46      gap: 1rem;
47    }
48
49    .flex {
50      display: flex;
51      align-items: center;
52      gap: 1rem;
53    }
54
55    @media (min-width: 768px) {
56      .container {
57        padding: 0 2rem;
58      }
59
60      .grid {
61        grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
62        gap: 2rem;
63      }
64    }
65  }
66
67  @layer components {
68    /* Composants UI */
69    .btn {
70      --btn-padding: 0.75rem 1.5rem;
71      --btn-border-radius: 6px;
72      --btn-font-weight: 500;
73      --btn-transition: all 0.2s ease;
74
75      display: inline-flex;
76      align-items: center;
77      justify-content: center;
78      padding: var(--btn-padding);
79      border: 1px solid transparent;
80      border-radius: var(--btn-border-radius);
81      font-weight: var(--btn-font-weight);
82      text-decoration: none;
83      cursor: pointer;
84      transition: var(--btn-transition);
85
86      &:focus {
87        outline: 2px solid transparent;
88        box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.5);
89      }
90
91      &:disabled {
92        opacity: 0.6;
93        cursor: not-allowed;
94      }
95    }
96
97    .btn-primary {
98      background: #007bff;
99      color: white;
100      border-color: #007bff;
101
102      &:hover:not(:disabled) {
103        background: #0056b3;
104        border-color: #0056b3;
105      }
106    }
107
108    .btn-secondary {
109      background: #6c757d;
110      color: white;
111      border-color: #6c757d;
112
113      &:hover:not(:disabled) {
114        background: #545b62;
115        border-color: #545b62;
116      }
117    }
118
119    .btn-outline {
120      background: transparent;
121      color: #007bff;
122      border-color: #007bff;
123
124      &:hover:not(:disabled) {
125        background: #007bff;
126        color: white;
127      }
128    }
129
130    /* Composant carte */
131    .card {
132      background: white;
133      border: 1px solid #e9ecef;
134      border-radius: 12px;
135      overflow: hidden;
136      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
137      transition: all 0.3s ease;
138
139      &:hover {
140        box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
141        transform: translateY(-2px);
142      }
143    }
144
145    .card-header {
146      padding: 1.5rem;
147      border-bottom: 1px solid #e9ecef;
148      background: #f8f9fa;
149    }
150
151    .card-body {
152      padding: 1.5rem;
153    }
154
155    .card-footer {
156      padding: 1rem 1.5rem;
157      border-top: 1px solid #e9ecef;
158      background: #f8f9fa;
159    }
160
161    /* Composant alerte */
162    .alert {
163      --alert-padding: 1rem 1.5rem;
164      --alert-border-radius: 8px;
165      --alert-border-width: 1px;
166
167      padding: var(--alert-padding);
168      border: var(--alert-border-width) solid transparent;
169      border-radius: var(--alert-border-radius);
170      margin-bottom: 1rem;
171
172      .alert-title {
173        font-weight: 600;
174        margin-bottom: 0.5rem;
175      }
176
177      &.alert-info {
178        background: #e3f2fd;
179        border-color: #2196f3;
180        color: #0d47a1;
181      }
182
183      &.alert-success {
184        background: #e8f5e8;
185        border-color: #4caf50;
186        color: #1b5e20;
187      }
188
189      &.alert-warning {
190        background: #fff3e0;
191        border-color: #ff9800;
192        color: #e65100;
193      }
194
195      &.alert-error {
196        background: #ffebee;
197        border-color: #f44336;
198        color: #b71c1c;
199      }
200    }
201  }
202}
203
204/* Styles externes (priorité plus élevée que le framework) */
205@layer application {
206  /* Surcharges spécifiques à l'application */
207  .btn-primary {
208    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
209    border: none;
210
211    &:hover:not(:disabled) {
212      background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
213    }
214  }
215
216  .card {
217    backdrop-filter: blur(10px);
218    background: rgba(255, 255, 255, 0.9);
219  }
220}
221
222/* Utilitaires - priorité la plus élevée */
223@layer utilities {
224  /* Classes utilitaires avec la priorité la plus élevée */
225  .text-left { text-align: left !important; }
226  .text-center { text-align: center !important; }
227  .text-right { text-align: right !important; }
228
229  .d-none { display: none !important; }
230  .d-block { display: block !important; }
231  .d-flex { display: flex !important; }
232  .d-grid { display: grid !important; }
233
234  .m-0 { margin: 0 !important; }
235  .m-1 { margin: 0.25rem !important; }
236  .m-2 { margin: 0.5rem !important; }
237  .m-3 { margin: 1rem !important; }
238  .m-4 { margin: 1.5rem !important; }
239  .m-5 { margin: 3rem !important; }
240
241  .p-0 { padding: 0 !important; }
242  .p-1 { padding: 0.25rem !important; }
243  .p-2 { padding: 0.5rem !important; }
244  .p-3 { padding: 1rem !important; }
245  .p-4 { padding: 1.5rem !important; }
246  .p-5 { padding: 3rem !important; }
247
248  /* Utilitaires réactifs */
249  @media (min-width: 768px) {
250    .md\:text-left { text-align: left !important; }
251    .md\:text-center { text-align: center !important; }
252    .md\:text-right { text-align: right !important; }
253
254    .md\:d-none { display: none !important; }
255    .md\:d-block { display: block !important; }
256    .md\:d-flex { display: flex !important; }
257  }
258
259  @media (min-width: 1024px) {
260    .lg\:text-left { text-align: left !important; }
261    .lg\:text-center { text-align: center !important; }
262    .lg\:text-right { text-align: right !important; }
263
264    .lg\:d-none { display: none !important; }
265    .lg\:d-block { display: block !important; }
266    .lg\:d-flex { display: flex !important; }
267  }
268}

Les couches imbriquées se rangent dans l'ordre à l'intérieur de leur parent, et tout le framework occupe une seule place dans la hiérarchie de la page. Depuis l'extérieur, tu désignes une couche interne avec une notation à point, par exemple @layer framework.reset.

2. Couches conditionnelles

À l'intérieur d'une couche, tu peux utiliser @media, @supports et @container, ainsi que des variables CSS pour les thèmes :

1/* Couches avec media queries */
2@layer responsive {
3  @media (max-width: 767px) {
4    .mobile-only {
5      display: block;
6    }
7
8    .desktop-only {
9      display: none;
10    }
11
12    .container {
13      padding: 0 1rem;
14    }
15
16    .grid {
17      grid-template-columns: 1fr;
18    }
19  }
20
21  @media (min-width: 768px) {
22    .mobile-only {
23      display: none;
24    }
25
26    .desktop-only {
27      display: block;
28    }
29
30    .container {
31      padding: 0 2rem;
32    }
33  }
34}
35
36/* Couches avec feature queries */
37@layer modern {
38  @supports (display: grid) {
39    .grid-layout {
40      display: grid;
41      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
42      gap: 1rem;
43    }
44  }
45
46  @supports not (display: grid) {
47    .grid-layout {
48      display: flex;
49      flex-wrap: wrap;
50      gap: 1rem;
51    }
52
53    .grid-layout > * {
54      flex: 1 1 250px;
55    }
56  }
57
58  @supports (container-type: inline-size) {
59    .responsive-component {
60      container-type: inline-size;
61    }
62
63    @container (min-width: 400px) {
64      .responsive-component .card {
65        display: flex;
66        align-items: center;
67        gap: 1rem;
68      }
69    }
70  }
71}
72
73/* Couches de thème */
74@layer themes {
75  [data-theme="light"] {
76    --bg-primary: #ffffff;
77    --bg-secondary: #f8f9fa;
78    --text-primary: #212529;
79    --text-secondary: #6c757d;
80    --border-color: #dee2e6;
81  }
82
83  [data-theme="dark"] {
84    --bg-primary: #1a1a1a;
85    --bg-secondary: #2d2d2d;
86    --text-primary: #ffffff;
87    --text-secondary: #b0b0b0;
88    --border-color: #404040;
89  }
90
91  [data-theme="blue"] {
92    --bg-primary: #0f1419;
93    --bg-secondary: #1e2a35;
94    --text-primary: #ffffff;
95    --text-secondary: #8fa3b0;
96    --border-color: #2a3f50;
97  }
98}

La condition décide seulement si les règles s'appliquent. La position de la couche dans la hiérarchie reste la même, quelle que soit la largeur de l'écran ou la prise en charge du navigateur.

Applications pratiques des Cascade Layers

1. Architecture CSS dans les grands projets

Voici à quoi peut ressembler l'architecture complète d'un grand projet avec sept couches. Le bloc est long, alors considère-le comme une carte à parcourir, et non comme un texte à apprendre par cœur :

1/* Définition de l'architecture principale */
2@layer normalize, base, layout, components, pages, themes, utilities;
3
4/* 1. Normalize/Reset */
5@layer normalize {
6  /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
7
8  html {
9    line-height: 1.15;
10    -webkit-text-size-adjust: 100%;
11  }
12
13  body {
14    margin: 0;
15  }
16
17  main {
18    display: block;
19  }
20
21  h1 {
22    font-size: 2em;
23    margin: 0.67em 0;
24  }
25
26  /* ... reste de normalize.css ... */
27}
28
29/* 2. Styles de base */
30@layer base {
31  :root {
32    /* Design tokens */
33    --font-family-base: 'Inter', system-ui, sans-serif;
34    --font-family-mono: 'JetBrains Mono', 'Fira Code', monospace;
35
36    --font-size-xs: 0.75rem;
37    --font-size-sm: 0.875rem;
38    --font-size-base: 1rem;
39    --font-size-lg: 1.125rem;
40    --font-size-xl: 1.25rem;
41    --font-size-2xl: 1.5rem;
42    --font-size-3xl: 1.875rem;
43    --font-size-4xl: 2.25rem;
44
45    --color-primary: #007bff;
46    --color-secondary: #6c757d;
47    --color-success: #28a745;
48    --color-warning: #ffc107;
49    --color-danger: #dc3545;
50    --color-info: #17a2b8;
51
52    --spacing-xs: 0.25rem;
53    --spacing-sm: 0.5rem;
54    --spacing-md: 1rem;
55    --spacing-lg: 1.5rem;
56    --spacing-xl: 2rem;
57    --spacing-2xl: 2.5rem;
58    --spacing-3xl: 3rem;
59
60    --border-radius-sm: 4px;
61    --border-radius-md: 8px;
62    --border-radius-lg: 12px;
63    --border-radius-xl: 16px;
64    --border-radius-full: 50%;
65
66    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
67    --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
68    --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.15);
69  }
70
71  body {
72    font-family: var(--font-family-base);
73    font-size: var(--font-size-base);
74    line-height: 1.6;
75    color: var(--text-primary, #212529);
76    background: var(--bg-primary, #ffffff);
77  }
78
79  h1, h2, h3, h4, h5, h6 {
80    margin-top: 0;
81    margin-bottom: var(--spacing-md);
82    font-weight: 600;
83    line-height: 1.2;
84    color: var(--text-primary, #212529);
85  }
86
87  p {
88    margin-top: 0;
89    margin-bottom: var(--spacing-md);
90    color: var(--text-secondary, #6c757d);
91  }
92
93  a {
94    color: var(--color-primary);
95    text-decoration: none;
96
97    &:hover {
98      text-decoration: underline;
99    }
100  }
101
102  img, svg {
103    display: block;
104    max-width: 100%;
105    height: auto;
106  }
107
108  code {
109    font-family: var(--font-family-mono);
110    font-size: 0.9em;
111    background: rgba(0, 0, 0, 0.1);
112    padding: 0.2em 0.4em;
113    border-radius: var(--border-radius-sm);
114  }
115}
116
117/* 3. Motifs de mise en page */
118@layer layout {
119  .container {
120    width: 100%;
121    max-width: 1200px;
122    margin: 0 auto;
123    padding: 0 var(--spacing-md);
124  }
125
126  .container-fluid {
127    width: 100%;
128    padding: 0 var(--spacing-md);
129  }
130
131  .row {
132    display: flex;
133    flex-wrap: wrap;
134    margin: 0 calc(var(--spacing-md) * -0.5);
135  }
136
137  .col {
138    flex: 1;
139    padding: 0 calc(var(--spacing-md) * 0.5);
140  }
141
142  .grid {
143    display: grid;
144    gap: var(--spacing-md);
145  }
146
147  .grid-auto {
148    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
149  }
150
151  .flex {
152    display: flex;
153  }
154
155  .flex-column {
156    flex-direction: column;
157  }
158
159  .items-center {
160    align-items: center;
161  }
162
163  .justify-center {
164    justify-content: center;
165  }
166
167  .justify-between {
168    justify-content: space-between;
169  }
170
171  .gap-sm { gap: var(--spacing-sm); }
172  .gap-md { gap: var(--spacing-md); }
173  .gap-lg { gap: var(--spacing-lg); }
174
175  @media (min-width: 768px) {
176    .container {
177      padding: 0 var(--spacing-lg);
178    }
179
180    .grid {
181      gap: var(--spacing-lg);
182    }
183
184    .md\:flex-row {
185      flex-direction: row;
186    }
187
188    .md\:gap-xl {
189      gap: var(--spacing-xl);
190    }
191  }
192
193  @media (min-width: 1024px) {
194    .container {
195      padding: 0 var(--spacing-xl);
196    }
197
198    .lg\:grid-cols-3 {
199      grid-template-columns: repeat(3, 1fr);
200    }
201
202    .lg\:grid-cols-4 {
203      grid-template-columns: repeat(4, 1fr);
204    }
205  }
206}
207
208/* 4. Composants réutilisables */
209@layer components {
210  /* Système de boutons */
211  .btn {
212    --btn-padding-y: var(--spacing-sm);
213    --btn-padding-x: var(--spacing-md);
214    --btn-font-size: var(--font-size-base);
215    --btn-border-radius: var(--border-radius-md);
216
217    display: inline-flex;
218    align-items: center;
219    justify-content: center;
220    padding: var(--btn-padding-y) var(--btn-padding-x);
221    font-size: var(--btn-font-size);
222    font-weight: 500;
223    border: 1px solid transparent;
224    border-radius: var(--btn-border-radius);
225    text-decoration: none;
226    cursor: pointer;
227    transition: all 0.2s ease;
228    user-select: none;
229
230    &:focus {
231      outline: 2px solid transparent;
232      box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.5);
233    }
234
235    &:disabled {
236      opacity: 0.6;
237      cursor: not-allowed;
238      pointer-events: none;
239    }
240
241    /* Variantes de taille */
242    &.btn-sm {
243      --btn-padding-y: var(--spacing-xs);
244      --btn-padding-x: var(--spacing-sm);
245      --btn-font-size: var(--font-size-sm);
246    }
247
248    &.btn-lg {
249      --btn-padding-y: var(--spacing-md);
250      --btn-padding-x: var(--spacing-lg);
251      --btn-font-size: var(--font-size-lg);
252    }
253
254    /* Variantes de couleur */
255    &.btn-primary {
256      background: var(--color-primary);
257      color: white;
258      border-color: var(--color-primary);
259
260      &:hover:not(:disabled) {
261        background: color-mix(in srgb, var(--color-primary) 90%, black);
262        border-color: color-mix(in srgb, var(--color-primary) 90%, black);
263      }
264    }
265
266    &.btn-secondary {
267      background: var(--color-secondary);
268      color: white;
269      border-color: var(--color-secondary);
270
271      &:hover:not(:disabled) {
272        background: color-mix(in srgb, var(--color-secondary) 90%, black);
273        border-color: color-mix(in srgb, var(--color-secondary) 90%, black);
274      }
275    }
276
277    &.btn-outline {
278      background: transparent;
279      color: var(--color-primary);
280      border-color: var(--color-primary);
281
282      &:hover:not(:disabled) {
283        background: var(--color-primary);
284        color: white;
285      }
286    }
287
288    &.btn-ghost {
289      background: transparent;
290      color: var(--color-primary);
291      border-color: transparent;
292
293      &:hover:not(:disabled) {
294        background: rgba(var(--color-primary-rgb, 0, 123, 255), 0.1);
295      }
296    }
297  }
298
299  /* Système de cartes */
300  .card {
301    background: var(--bg-primary, white);
302    border: 1px solid var(--border-color, #e9ecef);
303    border-radius: var(--border-radius-lg);
304    overflow: hidden;
305    box-shadow: var(--shadow-sm);
306    transition: all 0.3s ease;
307
308    &.card-hover:hover {
309      box-shadow: var(--shadow-lg);
310      transform: translateY(-2px);
311    }
312
313    .card-header {
314      padding: var(--spacing-lg);
315      border-bottom: 1px solid var(--border-color, #e9ecef);
316      background: var(--bg-secondary, #f8f9fa);
317
318      .card-title {
319        margin: 0 0 var(--spacing-xs) 0;
320        font-size: var(--font-size-lg);
321        font-weight: 600;
322        color: var(--text-primary);
323      }
324
325      .card-subtitle {
326        margin: 0;
327        font-size: var(--font-size-sm);
328        color: var(--text-secondary);
329      }
330    }
331
332    .card-body {
333      padding: var(--spacing-lg);
334    }
335
336    .card-footer {
337      padding: var(--spacing-md) var(--spacing-lg);
338      border-top: 1px solid var(--border-color, #e9ecef);
339      background: var(--bg-secondary, #f8f9fa);
340    }
341  }
342
343  /* Composants de formulaire */
344  .form-group {
345    margin-bottom: var(--spacing-md);
346  }
347
348  .form-label {
349    display: block;
350    margin-bottom: var(--spacing-xs);
351    font-weight: 500;
352    color: var(--text-primary);
353  }
354
355  .form-control {
356    width: 100%;
357    padding: var(--spacing-sm) var(--spacing-md);
358    font-size: var(--font-size-base);
359    border: 1px solid var(--border-color, #ced4da);
360    border-radius: var(--border-radius-md);
361    background: var(--bg-primary, white);
362    color: var(--text-primary);
363    transition: all 0.2s ease;
364
365    &:focus {
366      outline: none;
367      border-color: var(--color-primary);
368      box-shadow: 0 0 0 2px rgba(var(--color-primary-rgb, 0, 123, 255), 0.25);
369    }
370
371    &::placeholder {
372      color: var(--text-muted, #adb5bd);
373    }
374
375    &:disabled {
376      background: var(--bg-secondary, #f8f9fa);
377      opacity: 0.6;
378      cursor: not-allowed;
379    }
380  }
381
382  textarea.form-control {
383    resize: vertical;
384    min-height: 100px;
385  }
386
387  .form-text {
388    margin-top: var(--spacing-xs);
389    font-size: var(--font-size-sm);
390    color: var(--text-muted, #6c757d);
391  }
392
393  .form-error {
394    margin-top: var(--spacing-xs);
395    font-size: var(--font-size-sm);
396    color: var(--color-danger);
397  }
398}
399
400/* 5. Styles spécifiques aux pages */
401@layer pages {
402  .page-home {
403    .hero {
404      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
405      color: white;
406      padding: var(--spacing-3xl) 0;
407      text-align: center;
408
409      .hero-title {
410        font-size: var(--font-size-4xl);
411        margin-bottom: var(--spacing-md);
412      }
413
414      .hero-subtitle {
415        font-size: var(--font-size-xl);
416        opacity: 0.9;
417        margin-bottom: var(--spacing-xl);
418      }
419    }
420
421    .features {
422      padding: var(--spacing-3xl) 0;
423
424      .feature-grid {
425        display: grid;
426        grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
427        gap: var(--spacing-xl);
428      }
429
430      .feature-card {
431        text-align: center;
432        padding: var(--spacing-lg);
433
434        .feature-icon {
435          width: 60px;
436          height: 60px;
437          margin: 0 auto var(--spacing-md);
438          background: var(--color-primary);
439          border-radius: var(--border-radius-full);
440          display: flex;
441          align-items: center;
442          justify-content: center;
443          color: white;
444          font-size: var(--font-size-xl);
445        }
446
447        .feature-title {
448          font-size: var(--font-size-lg);
449          margin-bottom: var(--spacing-sm);
450        }
451
452        .feature-description {
453          color: var(--text-secondary);
454        }
455      }
456    }
457  }
458
459  .page-about {
460    .team-grid {
461      display: grid;
462      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
463      gap: var(--spacing-lg);
464    }
465
466    .team-member {
467      text-align: center;
468
469      .member-avatar {
470        width: 150px;
471        height: 150px;
472        border-radius: var(--border-radius-full);
473        margin: 0 auto var(--spacing-md);
474        object-fit: cover;
475      }
476
477      .member-name {
478        font-size: var(--font-size-lg);
479        margin-bottom: var(--spacing-xs);
480      }
481
482      .member-role {
483        color: var(--color-primary);
484        font-weight: 500;
485        margin-bottom: var(--spacing-sm);
486      }
487
488      .member-bio {
489        color: var(--text-secondary);
490        font-size: var(--font-size-sm);
491      }
492    }
493  }
494}
495
496/* 6. Surcharges de thème */
497@layer themes {
498  /* Thème clair (par défaut) */
499  :root,
500  [data-theme="light"] {
501    --bg-primary: #ffffff;
502    --bg-secondary: #f8f9fa;
503    --text-primary: #212529;
504    --text-secondary: #6c757d;
505    --text-muted: #adb5bd;
506    --border-color: #dee2e6;
507    --color-primary-rgb: 0, 123, 255;
508  }
509
510  /* Thème sombre */
511  [data-theme="dark"] {
512    --bg-primary: #1a1a1a;
513    --bg-secondary: #2d2d2d;
514    --text-primary: #ffffff;
515    --text-secondary: #b0b0b0;
516    --text-muted: #6c757d;
517    --border-color: #404040;
518
519    .card {
520      background: rgba(255, 255, 255, 0.05);
521      backdrop-filter: blur(10px);
522    }
523
524    .form-control {
525      background: var(--bg-secondary);
526      border-color: var(--border-color);
527
528      &:focus {
529        background: var(--bg-primary);
530      }
531    }
532  }
533
534  /* Thème bleu */
535  [data-theme="blue"] {
536    --bg-primary: #0f1419;
537    --bg-secondary: #1e2a35;
538    --text-primary: #ffffff;
539    --text-secondary: #8fa3b0;
540    --text-muted: #6c7b7f;
541    --border-color: #2a3f50;
542    --color-primary: #4fc3f7;
543    --color-primary-rgb: 79, 195, 247;
544  }
545
546  /* Préférence système */
547  @media (prefers-color-scheme: dark) {
548    :root:not([data-theme]) {
549      --bg-primary: #1a1a1a;
550      --bg-secondary: #2d2d2d;
551      --text-primary: #ffffff;
552      --text-secondary: #b0b0b0;
553      --text-muted: #6c757d;
554      --border-color: #404040;
555    }
556  }
557}
558
559/* 7. Classes utilitaires - priorité la plus élevée */
560@layer utilities {
561  /* Display */
562  .d-none { display: none !important; }
563  .d-block { display: block !important; }
564  .d-inline { display: inline !important; }
565  .d-inline-block { display: inline-block !important; }
566  .d-flex { display: flex !important; }
567  .d-grid { display: grid !important; }
568
569  /* Positionnement */
570  .position-relative { position: relative !important; }
571  .position-absolute { position: absolute !important; }
572  .position-fixed { position: fixed !important; }
573  .position-sticky { position: sticky !important; }
574
575  /* Alignement du texte */
576  .text-left { text-align: left !important; }
577  .text-center { text-align: center !important; }
578  .text-right { text-align: right !important; }
579  .text-justify { text-align: justify !important; }
580
581  /* Graisses de police */
582  .fw-normal { font-weight: 400 !important; }
583  .fw-medium { font-weight: 500 !important; }
584  .fw-semibold { font-weight: 600 !important; }
585  .fw-bold { font-weight: 700 !important; }
586
587  /* Couleurs */
588  .text-primary { color: var(--color-primary) !important; }
589  .text-secondary { color: var(--text-secondary) !important; }
590  .text-muted { color: var(--text-muted) !important; }
591  .text-success { color: var(--color-success) !important; }
592  .text-warning { color: var(--color-warning) !important; }
593  .text-danger { color: var(--color-danger) !important; }
594
595  /* Couleurs d'arrière-plan */
596  .bg-primary { background-color: var(--color-primary) !important; }
597  .bg-secondary { background-color: var(--bg-secondary) !important; }
598  .bg-success { background-color: var(--color-success) !important; }
599  .bg-warning { background-color: var(--color-warning) !important; }
600  .bg-danger { background-color: var(--color-danger) !important; }
601
602  /* Utilitaires d'espacement */
603  .m-0 { margin: 0 !important; }
604  .m-1 { margin: var(--spacing-xs) !important; }
605  .m-2 { margin: var(--spacing-sm) !important; }
606  .m-3 { margin: var(--spacing-md) !important; }
607  .m-4 { margin: var(--spacing-lg) !important; }
608  .m-5 { margin: var(--spacing-xl) !important; }
609
610  .p-0 { padding: 0 !important; }
611  .p-1 { padding: var(--spacing-xs) !important; }
612  .p-2 { padding: var(--spacing-sm) !important; }
613  .p-3 { padding: var(--spacing-md) !important; }
614  .p-4 { padding: var(--spacing-lg) !important; }
615  .p-5 { padding: var(--spacing-xl) !important; }
616
617  /* Marge/padding directionnels */
618  .mt-0 { margin-top: 0 !important; }
619  .mt-1 { margin-top: var(--spacing-xs) !important; }
620  .mt-2 { margin-top: var(--spacing-sm) !important; }
621  .mt-3 { margin-top: var(--spacing-md) !important; }
622  .mt-4 { margin-top: var(--spacing-lg) !important; }
623  .mt-5 { margin-top: var(--spacing-xl) !important; }
624
625  .mb-0 { margin-bottom: 0 !important; }
626  .mb-1 { margin-bottom: var(--spacing-xs) !important; }
627  .mb-2 { margin-bottom: var(--spacing-sm) !important; }
628  .mb-3 { margin-bottom: var(--spacing-md) !important; }
629  .mb-4 { margin-bottom: var(--spacing-lg) !important; }
630  .mb-5 { margin-bottom: var(--spacing-xl) !important; }
631
632  .pt-0 { padding-top: 0 !important; }
633  .pt-1 { padding-top: var(--spacing-xs) !important; }
634  .pt-2 { padding-top: var(--spacing-sm) !important; }
635  .pt-3 { padding-top: var(--spacing-md) !important; }
636  .pt-4 { padding-top: var(--spacing-lg) !important; }
637  .pt-5 { padding-top: var(--spacing-xl) !important; }
638
639  .pb-0 { padding-bottom: 0 !important; }
640  .pb-1 { padding-bottom: var(--spacing-xs) !important; }
641  .pb-2 { padding-bottom: var(--spacing-sm) !important; }
642  .pb-3 { padding-bottom: var(--spacing-md) !important; }
643  .pb-4 { padding-bottom: var(--spacing-lg) !important; }
644  .pb-5 { padding-bottom: var(--spacing-xl) !important; }
645
646  /* Utilitaires réactifs */
647  @media (min-width: 768px) {
648    .md\:d-none { display: none !important; }
649    .md\:d-block { display: block !important; }
650    .md\:d-flex { display: flex !important; }
651    .md\:text-left { text-align: left !important; }
652    .md\:text-center { text-align: center !important; }
653    .md\:text-right { text-align: right !important; }
654  }
655
656  @media (min-width: 1024px) {
657    .lg\:d-none { display: none !important; }
658    .lg\:d-block { display: block !important; }
659    .lg\:d-flex { display: flex !important; }
660    .lg\:text-left { text-align: left !important; }
661    .lg\:text-center { text-align: center !important; }
662    .lg\:text-right { text-align: right !important; }
663  }
664}

Chaque couche a une seule mission : pages se place au-dessus des composants, themes au-dessus des pages, et utilities tout en haut, car une classe utilitaire doit toujours l'emporter.

2. Intégration avec les frameworks CSS

Les couches résolvent le problème du début de la leçon. Tu places le code du framework dans les couches inférieures, et tes propres composants dans les couches supérieures :

1/* Intégration avec Tailwind CSS */
2@layer tailwind-base, components, tailwind-utilities;
3
4@layer tailwind-base {
5  /* Styles de base de Tailwind */
6  @tailwind base;
7}
8
9@layer components {
10  /* Composants personnalisés au-dessus de la base de Tailwind */
11  .btn-custom {
12    @apply px-4 py-2 bg-blue-500 text-white rounded-lg;
13
14    &:hover {
15      @apply bg-blue-600;
16    }
17  }
18
19  .card-custom {
20    @apply bg-white shadow-lg rounded-xl p-6;
21
22    .card-title {
23      @apply text-xl font-semibold mb-2;
24    }
25
26    .card-content {
27      @apply text-gray-600 leading-relaxed;
28    }
29  }
30}
31
32@layer tailwind-utilities {
33  /* Les utilitaires de Tailwind ont la priorité la plus élevée */
34  @tailwind utilities;
35}
36
37/* Intégration avec Bootstrap */
38@layer bootstrap-reboot, bootstrap-grid, components, bootstrap-utilities;
39
40@layer bootstrap-reboot {
41  /* Styles reboot de Bootstrap */
42}
43
44@layer bootstrap-grid {
45  /* Système de grille de Bootstrap */
46  .container {
47    width: 100%;
48    padding-right: 15px;
49    padding-left: 15px;
50    margin-right: auto;
51    margin-left: auto;
52  }
53
54  .row {
55    display: flex;
56    flex-wrap: wrap;
57    margin-right: -15px;
58    margin-left: -15px;
59  }
60
61  .col {
62    flex: 1 0 0%;
63    padding-right: 15px;
64    padding-left: 15px;
65  }
66}
67
68@layer components {
69  /* Composants personnalisés qui enrichissent Bootstrap */
70  .btn-gradient {
71    background: linear-gradient(45deg, #007bff, #0056b3);
72    border: none;
73
74    &:hover {
75      background: linear-gradient(45deg, #0056b3, #004085);
76    }
77  }
78
79  .card-modern {
80    backdrop-filter: blur(10px);
81    background: rgba(255, 255, 255, 0.9);
82    border: 1px solid rgba(255, 255, 255, 0.2);
83  }
84}
85
86@layer bootstrap-utilities {
87  /* Classes utilitaires de Bootstrap */
88}

L'exemple avec Tailwind est une ébauche pour la version 3, dans laquelle les directives @tailwind et @apply sont traitées par Tailwind lui-même. Tailwind 4 range ses styles dans les couches natives theme, base, components et utilities. Tu charges toute une feuille de style externe dans une couche avec l'écriture @import url("bootstrap.css") layer(bootstrap).

Bonnes pratiques des Cascade Layers

1. Stratégie de nommage et d'organisation

Le nom d'une couche doit indiquer le rôle des styles qu'elle contient. Compare les bons et les mauvais noms :

1/* Bien - hiérarchie logique des couches */
2@layer
3  reset,           /* Styles reset/normalize */
4  base,            /* Typographie et éléments de base */
5  layout,          /* Motifs de mise en page et systèmes de grille */
6  components,      /* Composants UI réutilisables */
7  pages,           /* Styles spécifiques aux pages */
8  themes,          /* Variations de thème */
9  utilities;       /* Classes utilitaires */
10
11/* Bien - noms de couches fonctionnels */
12@layer
13  normalize,
14  design-tokens,
15  typography,
16  forms,
17  navigation,
18  content,
19  interactions,
20  responsive,
21  print;
22
23/* Mauvais - noms de couches peu clairs */
24@layer layer1, layer2, layer3, misc, other;

Des noms comme layer1 ou misc ne veulent plus rien dire au bout d'un mois. Choisis un schéma et tiens-t'y dans tout le projet.

2. Gestion des priorités

Les couches remplacent la plupart des !important, car la priorité découle de l'ordre des couches, et non des points d'exclamation :

1/* Bien - gestion explicite des priorités */
2@layer framework, custom, overrides;
3
4@layer framework {
5  /* Code du framework CSS */
6  .button {
7    padding: 0.5rem 1rem;
8    background: #007bff;
9    color: white;
10  }
11}
12
13@layer custom {
14  /* Personnalisations spécifiques au projet */
15  .button {
16    border-radius: 8px;
17    font-weight: 500;
18  }
19}
20
21@layer overrides {
22  /* Surcharges critiques */
23  .button.important {
24    background: #dc3545 !important;
25  }
26}
27
28/* Mauvais - s'appuyer uniquement sur !important */
29.button {
30  background: #007bff !important;
31  padding: 0.5rem 1rem !important;
32  color: white !important;
33}

Attention au piège : !important inverse l'ordre des couches. Une déclaration importante de la couche framework l'emporterait sur une déclaration importante de overrides, et les déclarations importantes dans les couches battent les déclarations importantes situées hors des couches. C'est pourquoi tu dois réserver !important dans les couches à des cas vraiment exceptionnels.

3. Débogage et développement

Pendant le travail, tu peux ajouter des couches d'aide, et même déclarer des couches à l'intérieur de media queries :

1/* Aide au débogage des couches */
2@layer debug {
3  [data-debug="layers"] * {
4    position: relative;
5  }
6
7  [data-debug="layers"] *::before {
8    content: attr(data-layer, 'unknown');
9    position: absolute;
10    top: 0;
11    right: 0;
12    background: rgba(255, 0, 0, 0.8);
13    color: white;
14    font-size: 10px;
15    padding: 2px 4px;
16    pointer-events: none;
17    z-index: 9999;
18  }
19}
20
21/* Couches pour différents environnements */
22@layer development {
23  /* Styles réservés au développement */
24  .debug-grid {
25    background-image:
26      linear-gradient(rgba(255,0,0,0.1) 1px, transparent 1px),
27      linear-gradient(90deg, rgba(255,0,0,0.1) 1px, transparent 1px);
28    background-size: 20px 20px;
29  }
30
31  .debug-info {
32    position: fixed;
33    bottom: 10px;
34    right: 10px;
35    background: rgba(0, 0, 0, 0.8);
36    color: white;
37    padding: 10px;
38    font-family: monospace;
39    font-size: 12px;
40    border-radius: 4px;
41    z-index: 9999;
42  }
43}
44
45/* Chargement conditionnel de couches */
46@media (max-width: 767px) {
47  @layer mobile-specific {
48    .desktop-only {
49      display: none !important;
50    }
51
52    .mobile-nav {
53      display: block;
54    }
55  }
56}
57
58@media print {
59  @layer print {
60    * {
61      background: transparent !important;
62      color: black !important;
63      box-shadow: none !important;
64    }
65
66    .no-print {
67      display: none !important;
68    }
69  }
70}

Les outils de développement du navigateur affichent le nom de la couche à côté des règles, tu vérifieras donc facilement laquelle l'a emporté.

Les CSS Cascade Layers changent fondamentalement la façon dont le CSS est géré dans les grands projets, en offrant un contrôle précis sur l'ordre d'application des styles et en éliminant le besoin d'utiliser !important dans la plupart des cas. Elles fonctionnent dans tous les principaux navigateurs depuis 2022. Dans le bac à sable ci-dessous, décommente la déclaration @layer reset, base, components, utilities et observe quelle règle l'emporte.

Souviens-toi : les couches sont les étages d'une pyramide, car ce qui se voit au sommet dépend de l'étage, et non du poids d'une seule pierre.

Code de cette leçon : index.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <title>Cascade Layers @layer</title>
6    <link rel="stylesheet" href="style.css">
7</head>
8<body>
9    <div class="container">
10        <h1>Cascade Layers</h1>
11        <button class="btn">Bouton de base</button>
12        <button class="btn btn-primary">Bouton principal</button>
13        <button class="btn btn-gold">Bouton doré</button>
14        <p class="info">Les couches contrôlent la priorité du CSS sans !important.</p>
15    </div>
16</body>
17</html>

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. À quoi sert @layer en CSS ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Crée des cascade layers : @layer reset, base, components, utilities et observe comment elles contrôlent la priorité de la cascade.

  • Tri vertical

    Ordonne les couches @layer de la priorité la plus faible à la plus forte :

  • Clique dans l'ordre

    Ordonne la syntaxe d'une déclaration Cascade Layers :

Articles utiles