CSS Container Queries: Responsive Komponenten ohne Media Queries
Wie CSS Container Queries Komponenten auf ihren Eltern-Container statt auf den Viewport reagieren lassen — für wirklich wiederverwendbare Bausteine.

Media Queries reagieren auf den Viewport. Container Queries reagieren auf das Elternelement. Dieser Unterschied verändert, wie responsive Komponenten funktionieren. Eine Card-Komponente in einer Seitenleiste sollte anders dargestellt werden als dieselbe Card im Hauptinhaltsbereich — obwohl sich der Viewport nicht geändert hat.
Media Queries können nicht ausdrücken: „Wenn mein Elternelement schmal ist, stapele vertikal." Container Queries können das. Das ist das fehlende Stück, das wirklich wiederverwendbare responsive Komponenten möglich macht.
Das Problem mit Media Queries bei Komponenten
Media Queries koppeln das Komponenten-Layout an den Viewport. Das funktioniert, wenn jede Komponente in einer vorhersehbaren Breite lebt, bricht aber, wenn Komponenten in unterschiedlichen Containern wiederverwendet werden.
/* ❌ Media query — tied to viewport width, not component context */
.product-card {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.product-card {
grid-template-columns: 200px 1fr;
}
}
/* Problem: the same card in a 300px sidebar still gets the
horizontal layout at 768px viewport — it's too wide for the container *//* ✅ Container query — responds to the actual available space */
.card-container {
container-type: inline-size;
container-name: card;
}
.product-card {
display: grid;
grid-template-columns: 1fr;
}
@container card (min-width: 400px) {
.product-card {
grid-template-columns: 200px 1fr;
}
}
/* Now the card switches to horizontal layout only when
its container gives it 400px+ of space */Die Card in einer schmalen Seitenleiste bleibt gestapelt. Dieselbe Card in einem breiten Inhaltsbereich wechselt ins horizontale Layout. Kein JavaScript, keine bedingten Klassen, keine doppelten Komponenten.
Containment einrichten
Container Queries benötigen einen Containment-Kontext — dem Browser muss mitgeteilt werden, welches Element gemessen werden soll. Die Eigenschaft container-type legt diesen fest.
/* Three containment types */
/* inline-size: respond to width changes (most common) */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
/* size: respond to both width and height changes */
.dashboard-tile {
container-type: size;
container-name: tile;
}
/* normal: no containment (opt-out, default) */
.unrestricted {
container-type: normal;
}
/* Shorthand: combined name and type */
.panel {
container: panel / inline-size;
}<!-- The container is the parent, the query applies to children -->
<div class="sidebar"> <!-- containment context -->
<div class="product-card"> <!-- this component adapts -->
<img src="product.jpg" alt="Product photo" />
<div class="product-info">
<h3>Product Name</h3>
<p>Description text here</p>
</div>
</div>
</div>Ein Element mit container-type: inline-size sagt dem Browser: „Meine Kindelemente könnten meine Breite abfragen. Verfolge sie." Ohne diese Deklaration haben @container-Abfragen keinen Kontext, gegen den sie messen können.
Praktische Komponenten-Muster
Container Queries glänzen bei Komponenten, die in mehreren Layout-Kontexten auftauchen.
/* Navigation component: horizontal in wide containers, vertical in narrow */
.nav-container {
container: nav / inline-size;
}
.nav-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.nav-item {
padding: 8px 12px;
}
.nav-item .label {
display: none;
}
@container nav (min-width: 200px) {
.nav-item .label {
display: inline;
}
}
@container nav (min-width: 600px) {
.nav-list {
flex-direction: row;
gap: 8px;
}
}/* Stats card: adapts from compact to full layout */
.stats-container {
container: stats / inline-size;
}
.stat-card {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
}
.stat-card .chart {
display: none;
}
.stat-card .trend {
font-size: 12px;
}
@container stats (min-width: 300px) {
.stat-card {
flex-direction: column;
align-items: flex-start;
padding: 16px;
}
.stat-card .trend {
font-size: 14px;
}
}
@container stats (min-width: 500px) {
.stat-card .chart {
display: block;
height: 80px;
}
}Container-Query-Einheiten
Container Queries führen neue CSS-Einheiten ein, die relativ zu den Dimensionen des Containers sind. Diese Einheiten funktionieren innerhalb von @container-Blöcken und überall dort, wo ein Längenwert akzeptiert wird.
.card-wrapper {
container: card / inline-size;
}
.card-title {
/* cqw = 1% of container's inline size (width) */
font-size: clamp(14px, 4cqw, 24px);
/* cqh = 1% of container's block size (height) */
/* cqi = 1% of container's inline size */
/* cqb = 1% of container's block size */
/* cqmin = smaller of cqi and cqb */
/* cqmax = larger of cqi and cqb */
}
.card-image {
/* Image height relative to container width */
height: 50cqi;
object-fit: cover;
}
@container card (min-width: 400px) {
.card-title {
font-size: clamp(16px, 3cqw, 28px);
}
.card-image {
height: 30cqi;
}
}Container-Query-Einheiten lassen Typografie und Abstände relativ zum Container der Komponente skalieren, nicht zum Viewport. Eine Card in einer schmalen Spalte bekommt proportional kleineren Text ohne explizite Breakpoints.
Kombination mit CSS-Grid-Layouts
Container Queries funktionieren auf natürliche Weise mit CSS Grid, um Dashboard-Layouts zu erstellen, in denen sich jede Kachel unabhängig anpasst.
/* Dashboard grid — each tile is a container */
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
}
.dashboard-tile {
container: tile / inline-size;
border: 1px solid var(--border-color);
border-radius: 8px;
overflow: hidden;
}
/* Tile content adapts to available space */
.tile-header {
display: flex;
justify-content: space-between;
padding: 12px;
}
.tile-body {
padding: 12px;
}
.tile-actions {
display: none;
}
@container tile (min-width: 350px) {
.tile-header {
padding: 16px 20px;
}
.tile-body {
padding: 16px 20px;
}
.tile-actions {
display: flex;
gap: 8px;
padding: 12px 20px;
border-top: 1px solid var(--border-color);
}
}<div class="dashboard">
<!-- Each tile adapts based on how many columns the grid assigns -->
<div class="dashboard-tile">
<div class="tile-header">
<h3>Revenue</h3>
<span class="badge">+12%</span>
</div>
<div class="tile-body">
<span class="metric">$45,231</span>
</div>
<div class="tile-actions">
<button>Details</button>
<button>Export</button>
</div>
</div>
<!-- More tiles... -->
</div>Wenn das Grid einer Kachel eine schmale Spalte zuweist, wird tile-actions ausgeblendet. Bekommt die Kachel eine breitere Spalte, erscheinen die Aktionen. Kein JavaScript, keine Resize Observer.
Container Queries verschachteln
Container können verschachtelt werden. Ein Kindelement fragt seinen nächsten Vorfahren mit Containment ab, nicht den äußersten Container.
.page-layout {
container: page / inline-size;
}
.sidebar-panel {
container: sidebar / inline-size;
}
/* This queries the sidebar container, not the page */
@container sidebar (min-width: 250px) {
.sidebar-widget {
padding: 16px;
}
}
/* This queries the page container */
@container page (min-width: 1024px) {
.main-content {
max-width: 800px;
margin: 0 auto;
}
}
/* Named containers avoid ambiguity */
/* Without a name, @container queries the nearest ancestor container *//* ❌ Ambiguous — which container does this query? */
@container (min-width: 400px) {
.widget { /* ... */ }
}
/* ✅ Explicit — queries the named container */
@container sidebar (min-width: 400px) {
.widget { /* ... */ }
}Benenne Container beim Verschachteln immer. Unbenannte @container-Abfragen treffen auf den nächsten Container-Vorfahren, was bei Änderungen der DOM-Struktur unerwartete Ergebnisse liefern kann.
Migrationsstrategie
Du musst nicht alle Media Queries durch Container Queries ersetzen. Migriere Breakpoints auf Komponentenebene und behalte Media Queries auf Seitenebene.
/* Keep media queries for page layout */
@media (min-width: 768px) {
.page-layout {
display: grid;
grid-template-columns: 250px 1fr;
}
}
@media (min-width: 1200px) {
.page-layout {
grid-template-columns: 300px 1fr 250px;
}
}
/* Use container queries for components inside the layout */
.main-content {
container: main / inline-size;
}
.sidebar {
container: sidebar / inline-size;
}
@container main (min-width: 600px) {
.article-card {
grid-template-columns: 150px 1fr;
}
}
@container sidebar (min-width: 200px) {
.sidebar-card .description {
display: block;
}
}Media Queries verwalten den Seitenrahmen. Container Queries verwalten das Innere der Komponenten. Diese Aufteilung ist sauber und zukunftssicher.
Wichtigste Erkenntnisse
- Container Queries entkoppeln Komponenten vom Viewport — Komponenten reagieren auf ihren tatsächlich verfügbaren Platz
- Setze
container-type: inline-sizeauf Elternelemente — damit wird der Messkontext etabliert - Benenne deine Container — vermeidet Mehrdeutigkeit beim Verschachteln mehrerer Containment-Kontexte
- Nutze Container-Query-Einheiten (
cqw,cqi) — für Typografie und Abstände, die mit der Komponente skalieren - Behalte Media Queries für das Seitenlayout — migriere Breakpoints auf Komponentenebene zu Container Queries
- Kombiniere sie mit CSS Grid — Auto-fit-Grids plus Container Queries erzeugen vollständig adaptive Dashboards


