Kurs HTML i CSS · Moduł 9: Nowoczesny CSS

Cascade Layers - kontrolowanie kolejności w kaskadzie CSS

19 min czytania
W tej lekcji4

Dołączasz do projektu bibliotekę komponentów. Jej przyciski mają selektor w rodzaju #app .btn.btn-primary, a Twoja prosta klasa .button przegrywa z nim specyficznością. Dopisujesz !important, autor biblioteki robi to samo i zaczyna się wojna, w której nikt nie wie, dlaczego przycisk ma taki kolor. CSS Cascade Layers (@layer) to funkcjonalność CSS, która pozwala na jawne kontrolowanie kolejności kaskady stylów. Umożliwia organizowanie CSS w logiczne warstwy z określoną hierarchią ważności, co znacząco ułatwia zarządzanie złożonymi arkuszami stylów.

Podstawy Cascade Layers

Czym są Cascade Layers?

Cascade Layers pozwalają na definiowanie nazwanych warstw CSS, gdzie style z warstw wyższych w hierarchii mają pierwszeństwo przed stylami z warstw niższych, niezależnie od specyficzności selektorów. Oto cztery klasyczne warstwy:

1/* Definicja warstwy i jej stylów */
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}

Bez osobnej deklaracji kolejność ustala pierwsze pojawienie się nazwy, więc reset leży najniżej, a utilities najwyżej. Klasa .hidden z utilities wygra więc z każdą regułą z components, nawet o wyższej specyficzności. Specyficzność liczy się dopiero wewnątrz jednej warstwy. Zapamiętaj też, że zwykłe style zapisane poza warstwami wygrywają ze wszystkimi warstwami.

Deklarowanie kolejności warstw

Można jawnie zdefiniować kolejność warstw na początku arkusza stylów, w jednej krótkiej instrukcji zakończonej średnikiem:

1/* Definicja kolejności warstw - od najniższego do najwyższego priorytetu */
2@layer reset, base, layout, components, utilities;
3
4/* Teraz można używać warstw w dowolnej kolejności w kodzie */
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/* Kolejność definicji w kodzie nie ma znaczenia - liczy się kolejność w deklaracji @layer */

Kolejność bloków w kodzie nie ma znaczenia, liczy się kolejność w deklaracji @layer. Polecam Ci zawsze zaczynać arkusz od takiej linii, bo cała hierarchia jest wtedy widoczna w jednym miejscu, jak plan pięter wyryty przy wejściu do piramidy.

Zaawansowane techniki Cascade Layers

1. Zagnieżdżanie warstw

Warstwy mogą zawierać własne warstwy. Cały framework zamykamy w jednej warstwie, a w środku porządkujemy jego reset, bazę, układ i narzędzia:

1@layer framework {
2  /* Warstwy zagnieżdżone w warstwie 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    /* Komponenty 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    /* Card component */
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    /* Alert component */
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/* Style zewnętrzne (wyższy priorytet od framework) */
205@layer application {
206  /* Nadpisania specyficzne dla aplikacji */
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/* Utilities - najwyższy priorytet */
223@layer utilities {
224  /* Klasy pomocnicze o najwyższym priorytecie */
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  /* Responsive utilities */
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}

Zagnieżdżone warstwy ustawiają się w kolejności wewnątrz rodzica, a cały framework zajmuje jedno miejsce w hierarchii strony. Do wewnętrznej warstwy odwołasz się z zewnątrz zapisem z kropką, na przykład @layer framework.reset.

2. Warunkowe warstwy

Wewnątrz warstwy możesz używać @media, @supports i @container, a także zmiennych CSS dla motywów:

1/* Warstwy z 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/* Warstwy z 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/* Theme layers */
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}

Warunek decyduje tylko o tym, czy reguły zadziałają. Pozycja warstwy w hierarchii pozostaje ta sama bez względu na szerokość ekranu czy wsparcie przeglądarki.

Praktyczne zastosowania Cascade Layers

1. Architektura CSS w dużych projektach

Tak może wyglądać pełna architektura dużego projektu z siedmioma warstwami. Blok jest długi, więc potraktuj go jak mapę do przejrzenia, a nie tekst do nauki na pamięć:

1/* Definicja głównej architektury */
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  /* ... reszta normalize.css ... */
27}
28
29/* 2. Base styles */
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. Layout patterns */
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. Reusable components */
209@layer components {
210  /* Button system */
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    /* Size variants */
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    /* Color variants */
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  /* Card system */
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  /* Form components */
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. Page-specific styles */
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. Theme overrides */
497@layer themes {
498  /* Light theme (default) */
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  /* Dark theme */
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  /* Blue theme */
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  /* System preference */
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. Utility classes - highest priority */
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  /* Positioning */
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  /* Text alignment */
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  /* Font weights */
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  /* Colors */
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  /* Background colors */
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  /* Spacing utilities */
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  /* Margin/padding directional */
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  /* Responsive utilities */
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}

Każda warstwa ma jedno zadanie: pages stoi nad komponentami, themes nad stronami, a utilities na samym szczycie, bo klasa pomocnicza ma wygrać zawsze.

2. Integracja z frameworkami CSS

Warstwy rozwiązują problem z początku lekcji. Kod frameworka umieszczasz w niższych warstwach, a własne komponenty w wyższych:

1/* Integracja z Tailwind CSS */
2@layer tailwind-base, components, tailwind-utilities;
3
4@layer tailwind-base {
5  /* Tailwind's base styles */
6  @tailwind base;
7}
8
9@layer components {
10  /* Custom components over Tailwind's base */
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  /* Tailwind's utilities have highest priority */
34  @tailwind utilities;
35}
36
37/* Integracja z Bootstrap */
38@layer bootstrap-reboot, bootstrap-grid, components, bootstrap-utilities;
39
40@layer bootstrap-reboot {
41  /* Bootstrap's reboot styles */
42}
43
44@layer bootstrap-grid {
45  /* Bootstrap's grid system */
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  /* Custom components that enhance 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  /* Bootstrap's utility classes */
88}

Przykład z Tailwindem to szkic dla wersji 3, w której dyrektywy @tailwind i @apply przetwarza sam Tailwind. Tailwind 4 układa swoje style w natywnych warstwach theme, base, components i utilities. Cały zewnętrzny arkusz wczytasz do warstwy zapisem @import url("bootstrap.css") layer(bootstrap).

Najlepsze praktyki Cascade Layers

1. Strategia nazewnictwa i organizacji

Nazwa warstwy powinna mówić, jaką rolę pełnią zawarte w niej style. Porównaj dobre i złe nazwy:

1/* Dobrze - logiczna hierarchia warstw */
2@layer
3  reset,           /* Reset/normalize styles */
4  base,            /* Base typography and elements */
5  layout,          /* Layout patterns and grid systems */
6  components,      /* Reusable UI components */
7  pages,           /* Page-specific styles */
8  themes,          /* Theme variations */
9  utilities;       /* Utility classes */
10
11/* Dobrze - funkcjonalne nazwy warstw */
12@layer
13  normalize,
14  design-tokens,
15  typography,
16  forms,
17  navigation,
18  content,
19  interactions,
20  responsive,
21  print;
22
23/* Źle - niejasne nazwy warstw */
24@layer layer1, layer2, layer3, misc, other;

Nazwy w rodzaju layer1 czy misc po miesiącu nic nie znaczą. Wybierz jeden schemat i trzymaj się go w całym projekcie.

2. Zarządzanie priorytetami

Warstwy zastępują większość !important, bo priorytet wynika z kolejności warstw, a nie z wykrzykników:

1/* Dobrze - jawne zarządzanie priorytetami */
2@layer framework, custom, overrides;
3
4@layer framework {
5  /* Kod frameworka CSS */
6  .button {
7    padding: 0.5rem 1rem;
8    background: #007bff;
9    color: white;
10  }
11}
12
13@layer custom {
14  /* Dostosowania specyficzne dla projektu */
15  .button {
16    border-radius: 8px;
17    font-weight: 500;
18  }
19}
20
21@layer overrides {
22  /* Krytyczne nadpisania */
23  .button.important {
24    background: #dc3545 !important;
25  }
26}
27
28/* Źle - poleganie tylko na !important */
29.button {
30  background: #007bff !important;
31  padding: 0.5rem 1rem !important;
32  color: white !important;
33}

Uwaga na pułapkę: !important odwraca kolejność warstw. Ważna deklaracja z warstwy framework wygrałaby z ważną deklaracją z overrides, a ważne deklaracje w warstwach biją ważne deklaracje spoza warstw. Dlatego !important w warstwach stosuj naprawdę wyjątkowo.

3. Debugging i rozwój

Na czas pracy możesz dodać warstwy pomocnicze, a nawet deklarować warstwy wewnątrz media queries:

1/* Helper do debugowania warstw */
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/* Warstwy dla różnych środowisk */
22@layer development {
23  /* Style tylko dla developmentu */
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/* Conditional loading warstw */
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}

Narzędzia deweloperskie przeglądarki pokazują przy regułach nazwę warstwy, więc łatwo sprawdzisz, która z nich wygrała.

CSS Cascade Layers fundamentalnie zmieniają sposób zarządzania CSS w dużych projektach, oferując precyzyjną kontrolę nad kolejnością stosowania stylów i eliminując konieczność używania !important w większości przypadków. Działają we wszystkich głównych przeglądarkach od 2022 roku. W piaskownicy poniżej odkomentuj deklarację @layer reset, base, components, utilities i obserwuj, która reguła wygrywa.

Pamiętaj: warstwy to piętra piramidy, bo o tym, co widać na szczycie, decyduje piętro, a nie ciężar pojedynczego kamienia.

Kod do tej lekcji: index.html
1<!DOCTYPE html>
2<html lang="pl">
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">Przycisk bazowy</button>
12        <button class="btn btn-primary">Przycisk glowny</button>
13        <button class="btn btn-gold">Przycisk zloty</button>
14        <p class="info">Warstwy kontroluja priorytet CSS bez !important.</p>
15    </div>
16</body>
17</html>

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Do czego służy @layer w CSS?

Zadania praktyczne w grze

  • Edytor kodu

    Stwórz cascade layers: @layer reset, base, components, utilities i zobacz jak kontrolują priorytet kaskady.

  • Układanie w pionie

    Ułóż warstwy @layer od najniższego do najwyższego priorytetu:

  • Klikanie w kolejności

    Ułóż składnię deklaracji Cascade Layers:

Przydatne artykuły