Container Queries to rewolucyjna funkcjonalność CSS, która umożliwia tworzenie komponentów responsywnych w oparciu o rozmiar ich kontenera, a nie okna przeglądarki. To przełom w projektowaniu komponentów, który pozwala na prawdziwie modularne i elastyczne układy.
Container Queries pozwalają na stosowanie stylów w zależności od rozmiaru kontenera rodzica, a nie viewport. To oznacza, że komponenty mogą reagować na swój lokalny kontekst, co czyni je bardziej przewidywalnymi i wielokrotniego użytku.
1/* Tradycyjne Media Queries - bazują na viewport */
2@media (max-width: 768px) {
3 .card {
4 flex-direction: column;
5 }
6}
7
8/* Container Queries - bazują na rozmiarze kontenera */
9@container (max-width: 400px) {
10 .card {
11 flex-direction: column;
12 }
13}Aby używać Container Queries, najpierw musimy zdefiniować element jako kontener:
1.container {
2 container-type: inline-size; /* Monitoruje szerokość */
3}
4
5.container {
6 container-type: size; /* Monitoruje szerokość i wysokość */
7}
8
9.container {
10 container-type: normal; /* Wartość domyślna - brak monitorowania */
11}
12
13/* Skrócona forma z nazwą kontenera */
14.sidebar {
15 container: sidebar / inline-size;
16}1.product-grid {
2 display: grid;
3 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
4 gap: 1rem;
5 container-type: inline-size;
6}
7
8.product-card {
9 background: white;
10 border-radius: 8px;
11 padding: 1rem;
12 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
13}
14
15/* Gdy kontener jest wąski - układ pionowy */
16@container (max-width: 300px) {
17 .product-card {
18 text-align: center;
19 }
20
21 .product-card .image {
22 width: 100%;
23 margin-bottom: 1rem;
24 }
25
26 .product-card .title {
27 font-size: 1.1rem;
28 margin-bottom: 0.5rem;
29 }
30
31 .product-card .price {
32 font-size: 1.2rem;
33 color: #e74c3c;
34 }
35}
36
37/* Gdy kontener jest szerszy - układ poziomy */
38@container (min-width: 400px) {
39 .product-card {
40 display: flex;
41 align-items: center;
42 gap: 1rem;
43 }
44
45 .product-card .image {
46 width: 120px;
47 height: 120px;
48 flex-shrink: 0;
49 }
50
51 .product-card .content {
52 flex: 1;
53 }
54
55 .product-card .title {
56 font-size: 1.3rem;
57 margin-bottom: 0.5rem;
58 }
59
60 .product-card .description {
61 color: #666;
62 margin-bottom: 1rem;
63 }
64
65 .product-card .price {
66 font-size: 1.4rem;
67 font-weight: bold;
68 color: #27ae60;
69 }
70}1.navigation {
2 container-name: nav;
3 container-type: inline-size;
4}
5
6.nav-list {
7 display: flex;
8 list-style: none;
9 margin: 0;
10 padding: 0;
11 gap: 1rem;
12}
13
14/* Kompaktowa nawigacja dla małych kontenerów */
15@container nav (max-width: 600px) {
16 .nav-list {
17 flex-direction: column;
18 gap: 0;
19 }
20
21 .nav-item {
22 border-bottom: 1px solid #eee;
23 }
24
25 .nav-link {
26 display: block;
27 padding: 1rem;
28 text-decoration: none;
29 transition: background-color 0.2s;
30 }
31
32 .nav-link:hover {
33 background-color: #f8f9fa;
34 }
35
36 /* Ukryj teksty i pokaż tylko ikony */
37 .nav-text {
38 display: none;
39 }
40
41 .nav-icon {
42 display: block;
43 font-size: 1.5rem;
44 }
45}
46
47/* Pełna nawigacja dla większych kontenerów */
48@container nav (min-width: 601px) {
49 .nav-list {
50 justify-content: space-between;
51 align-items: center;
52 }
53
54 .nav-link {
55 display: flex;
56 align-items: center;
57 gap: 0.5rem;
58 padding: 0.5rem 1rem;
59 text-decoration: none;
60 border-radius: 4px;
61 transition: all 0.2s;
62 }
63
64 .nav-link:hover {
65 background-color: #007bff;
66 color: white;
67 }
68
69 .nav-text {
70 display: inline;
71 }
72
73 .nav-icon {
74 font-size: 1.2rem;
75 }
76}1.dashboard {
2 display: grid;
3 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
4 gap: 1rem;
5 padding: 1rem;
6}
7
8.widget {
9 background: white;
10 border-radius: 8px;
11 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
12 container-type: inline-size;
13 overflow: hidden;
14}
15
16.widget-header {
17 padding: 1rem;
18 background: #f8f9fa;
19 border-bottom: 1px solid #dee2e6;
20}
21
22.widget-content {
23 padding: 1rem;
24}
25
26/* Mały widget - podstawowe informacje */
27@container (max-width: 350px) {
28 .widget-header h3 {
29 font-size: 1rem;
30 margin: 0;
31 }
32
33 .widget-stats {
34 display: block;
35 }
36
37 .stat-item {
38 margin-bottom: 0.5rem;
39 text-align: center;
40 }
41
42 .stat-value {
43 font-size: 1.5rem;
44 font-weight: bold;
45 display: block;
46 }
47
48 .stat-label {
49 font-size: 0.8rem;
50 color: #666;
51 }
52
53 .chart {
54 height: 150px;
55 }
56}
57
58/* Średni widget - więcej szczegółów */
59@container (min-width: 351px) and (max-width: 500px) {
60 .widget-header h3 {
61 font-size: 1.2rem;
62 margin: 0;
63 }
64
65 .widget-stats {
66 display: flex;
67 justify-content: space-around;
68 margin-bottom: 1rem;
69 }
70
71 .stat-item {
72 text-align: center;
73 }
74
75 .stat-value {
76 font-size: 1.8rem;
77 font-weight: bold;
78 display: block;
79 color: #007bff;
80 }
81
82 .stat-label {
83 font-size: 0.9rem;
84 color: #666;
85 }
86
87 .chart {
88 height: 200px;
89 }
90}
91
92/* Duży widget - pełne detale */
93@container (min-width: 501px) {
94 .widget-header {
95 display: flex;
96 justify-content: space-between;
97 align-items: center;
98 }
99
100 .widget-header h3 {
101 font-size: 1.4rem;
102 margin: 0;
103 }
104
105 .widget-actions {
106 display: flex;
107 gap: 0.5rem;
108 }
109
110 .widget-stats {
111 display: grid;
112 grid-template-columns: repeat(3, 1fr);
113 gap: 1rem;
114 margin-bottom: 1.5rem;
115 }
116
117 .stat-item {
118 text-align: center;
119 padding: 1rem;
120 background: #f8f9fa;
121 border-radius: 4px;
122 }
123
124 .stat-value {
125 font-size: 2rem;
126 font-weight: bold;
127 display: block;
128 color: #28a745;
129 }
130
131 .stat-label {
132 font-size: 1rem;
133 color: #666;
134 margin-top: 0.5rem;
135 }
136
137 .chart {
138 height: 250px;
139 }
140
141 .widget-details {
142 display: block;
143 margin-top: 1rem;
144 padding-top: 1rem;
145 border-top: 1px solid #dee2e6;
146 }
147}1.component {
2 container-type: inline-size;
3}
4
5/* Połączenie warunków */
6@container (min-width: 300px) and (max-width: 600px) {
7 .component {
8 background: lightblue;
9 }
10}
11
12/* Alternatywa */
13@container (max-width: 300px), (min-width: 800px) {
14 .component {
15 border: 2px solid red;
16 }
17}
18
19/* Negacja */
20@container not (min-width: 400px) {
21 .component {
22 font-size: 0.9rem;
23 }
24}1.main-container {
2 container-name: main;
3 container-type: inline-size;
4}
5
6.sidebar {
7 container-name: sidebar;
8 container-type: inline-size;
9}
10
11.article {
12 container-name: article;
13 container-type: inline-size;
14}
15
16/* Różne style w zależności od kontekstu */
17@container main (min-width: 1200px) {
18 .layout {
19 display: grid;
20 grid-template-columns: 300px 1fr;
21 gap: 2rem;
22 }
23}
24
25@container sidebar (max-width: 250px) {
26 .sidebar-item {
27 padding: 0.5rem;
28 }
29
30 .sidebar-text {
31 display: none;
32 }
33}
34
35@container article (min-width: 600px) {
36 .article-content {
37 columns: 2;
38 column-gap: 2rem;
39 }
40}1.gallery {
2 container-type: inline-size;
3}
4
5/* Responsywna galeria bazująca na rozmiarze kontenera */
6@container (max-width: 400px) {
7 .gallery {
8 display: grid;
9 grid-template-columns: 1fr;
10 gap: 1rem;
11 }
12}
13
14@container (min-width: 401px) and (max-width: 800px) {
15 .gallery {
16 display: grid;
17 grid-template-columns: repeat(2, 1fr);
18 gap: 1rem;
19 }
20}
21
22@container (min-width: 801px) and (max-width: 1200px) {
23 .gallery {
24 display: grid;
25 grid-template-columns: repeat(3, 1fr);
26 gap: 1.5rem;
27 }
28}
29
30@container (min-width: 1201px) {
31 .gallery {
32 display: grid;
33 grid-template-columns: repeat(4, 1fr);
34 gap: 2rem;
35 }
36}
37
38/* Adaptacyjne zdjęcia */
39.gallery-item {
40 position: relative;
41 overflow: hidden;
42 border-radius: 8px;
43}
44
45@container (max-width: 400px) {
46 .gallery-item {
47 aspect-ratio: 16/9;
48 }
49
50 .gallery-item img {
51 width: 100%;
52 height: 100%;
53 object-fit: cover;
54 }
55
56 .gallery-caption {
57 position: absolute;
58 bottom: 0;
59 left: 0;
60 right: 0;
61 background: linear-gradient(transparent, rgba(0,0,0,0.8));
62 color: white;
63 padding: 1rem;
64 font-size: 0.9rem;
65 }
66}
67
68@container (min-width: 801px) {
69 .gallery-item {
70 aspect-ratio: 1;
71 transition: transform 0.3s ease;
72 }
73
74 .gallery-item:hover {
75 transform: scale(1.05);
76 }
77
78 .gallery-caption {
79 position: absolute;
80 bottom: 0;
81 left: 0;
82 right: 0;
83 background: rgba(0,0,0,0.9);
84 color: white;
85 padding: 1rem;
86 transform: translateY(100%);
87 transition: transform 0.3s ease;
88 }
89
90 .gallery-item:hover .gallery-caption {
91 transform: translateY(0);
92 }
93}1/* ✅ Dobre zastosowanie Container Queries */
2.component {
3 container-type: inline-size;
4}
5
6/* Komponenty, które mogą być używane w różnych kontekstach */
7@container (max-width: 300px) {
8 .card {
9 /* Style dla wąskiego kontenera */
10 flex-direction: column;
11 }
12}
13
14/* ✅ Media Queries dla globalnych zmian */
15@media (max-width: 768px) {
16 body {
17 font-size: 14px;
18 }
19
20 .global-header {
21 padding: 1rem;
22 }
23}
24
25/* ❌ Nie używaj Container Queries dla globalnych zmian */
26@container (max-width: 768px) {
27 body { /* To nie zadziała jak oczekujesz */
28 font-size: 14px;
29 }
30}1<!-- HTML -->
2<div class="page-layout">
3 <main class="main-content">
4 <div class="card-container">
5 <div class="profile-card">
6 <img src="avatar.jpg" alt="Profil" class="avatar">
7 <div class="info">
8 <h3 class="name">Jan Kowalski</h3>
9 <p class="role">Frontend Developer</p>
10 <p class="description">Passionate about creating amazing web experiences</p>
11 <div class="actions">
12 <button class="btn primary">Follow</button>
13 <button class="btn secondary">Message</button>
14 </div>
15 </div>
16 </div>
17 </div>
18 </main>
19
20 <aside class="sidebar">
21 <div class="card-container">
22 <div class="profile-card">
23 <!-- Ta sama karta, ale w węższym kontenerze -->
24 <img src="avatar.jpg" alt="Profil" class="avatar">
25 <div class="info">
26 <h3 class="name">Jan Kowalski</h3>
27 <p class="role">Frontend Developer</p>
28 <p class="description">Passionate about creating amazing web experiences</p>
29 <div class="actions">
30 <button class="btn primary">Follow</button>
31 <button class="btn secondary">Message</button>
32 </div>
33 </div>
34 </div>
35 </div>
36 </aside>
37</div>1/* CSS z Container Queries */
2.card-container {
3 container-type: inline-size;
4}
5
6.profile-card {
7 background: white;
8 border-radius: 12px;
9 padding: 1.5rem;
10 box-shadow: 0 4px 6px rgba(0,0,0,0.1);
11 transition: all 0.3s ease;
12}
13
14/* Wąski kontener - układ pionowy */
15@container (max-width: 350px) {
16 .profile-card {
17 text-align: center;
18 }
19
20 .avatar {
21 width: 80px;
22 height: 80px;
23 border-radius: 50%;
24 margin: 0 auto 1rem;
25 display: block;
26 }
27
28 .name {
29 font-size: 1.2rem;
30 margin-bottom: 0.5rem;
31 }
32
33 .role {
34 color: #666;
35 font-size: 0.9rem;
36 margin-bottom: 1rem;
37 }
38
39 .description {
40 display: none; /* Ukryj w wąskim kontenerze */
41 }
42
43 .actions {
44 display: flex;
45 flex-direction: column;
46 gap: 0.5rem;
47 }
48
49 .btn {
50 padding: 0.75rem;
51 border: none;
52 border-radius: 6px;
53 font-size: 0.9rem;
54 cursor: pointer;
55 transition: all 0.2s;
56 }
57
58 .btn.primary {
59 background: #007bff;
60 color: white;
61 }
62
63 .btn.secondary {
64 background: #f8f9fa;
65 color: #666;
66 }
67}
68
69/* Średni kontener - układ mieszany */
70@container (min-width: 351px) and (max-width: 500px) {
71 .profile-card {
72 display: flex;
73 align-items: center;
74 gap: 1rem;
75 text-align: left;
76 }
77
78 .avatar {
79 width: 100px;
80 height: 100px;
81 border-radius: 50%;
82 flex-shrink: 0;
83 }
84
85 .info {
86 flex: 1;
87 }
88
89 .name {
90 font-size: 1.3rem;
91 margin-bottom: 0.25rem;
92 }
93
94 .role {
95 color: #666;
96 font-size: 1rem;
97 margin-bottom: 0.5rem;
98 }
99
100 .description {
101 font-size: 0.9rem;
102 color: #888;
103 margin-bottom: 1rem;
104 display: -webkit-box;
105 -webkit-line-clamp: 2;
106 -webkit-box-orient: vertical;
107 overflow: hidden;
108 }
109
110 .actions {
111 display: flex;
112 gap: 0.5rem;
113 }
114
115 .btn {
116 padding: 0.5rem 1rem;
117 border: none;
118 border-radius: 6px;
119 font-size: 0.9rem;
120 cursor: pointer;
121 transition: all 0.2s;
122 }
123
124 .btn.primary {
125 background: #007bff;
126 color: white;
127 }
128
129 .btn.secondary {
130 background: #f8f9fa;
131 color: #666;
132 border: 1px solid #dee2e6;
133 }
134}
135
136/* Szeroki kontener - pełny układ */
137@container (min-width: 501px) {
138 .profile-card {
139 display: flex;
140 align-items: flex-start;
141 gap: 1.5rem;
142 }
143
144 .avatar {
145 width: 120px;
146 height: 120px;
147 border-radius: 50%;
148 flex-shrink: 0;
149 }
150
151 .info {
152 flex: 1;
153 }
154
155 .name {
156 font-size: 1.5rem;
157 margin-bottom: 0.5rem;
158 font-weight: 600;
159 }
160
161 .role {
162 color: #007bff;
163 font-size: 1.1rem;
164 margin-bottom: 1rem;
165 font-weight: 500;
166 }
167
168 .description {
169 font-size: 1rem;
170 color: #555;
171 line-height: 1.6;
172 margin-bottom: 1.5rem;
173 }
174
175 .actions {
176 display: flex;
177 gap: 1rem;
178 }
179
180 .btn {
181 padding: 0.75rem 1.5rem;
182 border: none;
183 border-radius: 8px;
184 font-size: 1rem;
185 font-weight: 500;
186 cursor: pointer;
187 transition: all 0.2s;
188 }
189
190 .btn.primary {
191 background: #007bff;
192 color: white;
193 }
194
195 .btn.primary:hover {
196 background: #0056b3;
197 }
198
199 .btn.secondary {
200 background: white;
201 color: #666;
202 border: 2px solid #dee2e6;
203 }
204
205 .btn.secondary:hover {
206 border-color: #007bff;
207 color: #007bff;
208 }
209}1/* ✅ Użyj tylko potrzebnego typu kontenera */
2.width-only {
3 container-type: inline-size; /* Lepsze dla wydajności */
4}
5
6.both-dimensions {
7 container-type: size; /* Tylko gdy potrzebujesz wysokości */
8}
9
10/* ✅ Grupuj podobne breakpointy */
11@container (max-width: 400px) {
12 .component-a { /* style */ }
13 .component-b { /* style */ }
14 .component-c { /* style */ }
15}
16
17/* ❌ Unikaj nadmiernego zagnieżdżania */
18@container (min-width: 300px) {
19 @container (max-width: 600px) { /* Trudne do zarządzania */
20 .component { /* style */ }
21 }
22}1/* Fallback bez Container Queries */
2.responsive-component {
3 padding: 1rem;
4 background: #f8f9fa;
5}
6
7.responsive-component .title {
8 font-size: 1.2rem;
9 margin-bottom: 1rem;
10}
11
12.responsive-component .content {
13 display: block;
14}
15
16/* Container Queries jako enhancement */
17@supports (container-type: inline-size) {
18 .responsive-container {
19 container-type: inline-size;
20 }
21
22 @container (min-width: 400px) {
23 .responsive-component {
24 padding: 2rem;
25 }
26
27 .responsive-component .title {
28 font-size: 1.5rem;
29 }
30
31 .responsive-component .content {
32 display: flex;
33 gap: 1rem;
34 }
35 }
36}Container Queries wprowadzają specjalne jednostki, które odnoszą się do rozmiaru kontenera:
| Jednostka | Opis | |-----------|------| |
cqw | 1% szerokości kontenera (container query width) |
| cqh | 1% wysokości kontenera (container query height) |
| cqi | 1% rozmiaru inline kontenera (container query inline) |
| cqb | 1% rozmiaru block kontenera (container query block) |1@container (min-width: 400px) {
2 .card-title {
3 font-size: 5cqw; /* 5% szerokości kontenera */
4 }
5}Jednostki te pozwalają na płynne skalowanie elementów w zależności od rozmiaru kontenera, podobnie jak
vw/vh działają względem viewport.Container Queries reprezentują przyszłość responsywnego web designu, umożliwiając tworzenie prawdziwie modularnych komponentów, które adaptują się do swojego kontekstu niezależnie od rozmiaru viewport. To narzędzie, które fundamentalnie zmienia sposób myślenia o responsywności w CSS.