Cours HTML et CSS · Module 9: CSS moderne
Cascade Layers - Contrôler l'ordre de la cascade CSS
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. À 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 :