Kurs HTML i CSS · Moduł 9: Nowoczesny CSS
Cascade Layers - kontrolowanie kolejności w kaskadzie CSS
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. 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: