CSS Container Queries: Ein praktischer Leitfaden
Media Queries beantworten eine Frage: Wie groß ist der Viewport? Für die meisten Komponenten ist das die falsche Frage, denn eine Karte, ein Sidebar-Widget oder eine Produktkachel interessiert sich nicht dafür, wie breit das Browserfenster ist – sondern dafür, wie viel Platz sie hat, was davon abhängt, wo sie im Layout platziert wird. Container Queries beantworten die Frage, die Komponenten tatsächlich beantwortet haben müssen.
Das Problem mit rein Media-Query-basiertem Responsive Design
Angenommen, du baust eine <card>-Komponente, die von einem horizontalen auf ein vertikales Layout wechseln soll, wenn sie schmal wird. Mit Media Queries musst du die Viewport-Breite erraten, bei der das passiert:
.card {
display: flex;
flex-direction: row;
}
@media (max-width: 500px) {
.card {
flex-direction: column;
}
}
Das funktioniert gut, wenn die Karte immer fast die volle Viewport-Breite einnimmt. Platziert man dieselbe Karte aber in einer schmalen Sidebar auf einem 1400 px breiten Desktop-Bildschirm, bekommt sie nie das vertikale Layout, das sie eigentlich braucht – der Viewport ist riesig, obwohl der verfügbare Platz der Karte winzig ist. Das Aussehen der Komponente ist an etwas gekoppelt, worauf sie keinen Einfluss hat: wo ihr Elternelement sie platziert.
Einen Containment-Kontext deklarieren
Container Queries drehen die Frage um: Statt „wie breit ist der Viewport" fragt man „wie breit ist mein nächster Vorfahr, der sich als Query-Container eingetragen hat". Ein Element trägt man mit container-type ein:
.card-grid {
container-type: inline-size;
container-name: card-grid;
}
inline-size bedeutet „verfolge die Breite des Containers" (der übliche Fall für horizontale Layout-Entscheidungen). container-name ist optional – damit kann man einen bestimmten Vorfahren-Container ansprechen, wenn mehrere verschachtelte Container im Spiel sind, statt nur „den nächsten" abzufragen.
Gegen den Container abfragen
Ist Containment deklariert, können Nachfahren die Größe dieses Containers abfragen statt die des Viewports:
.card {
display: flex;
flex-direction: column;
gap: 8px;
}
@container card-grid (min-width: 400px) {
.card {
flex-direction: row;
gap: 16px;
}
}
Jetzt ordnet sich dieselbe .card-Komponente horizontal an, wenn ihr 400 px+ Containerbreite zur Verfügung stehen – egal, ob weil sie allein auf einer breiten Seite steht oder weil sie eine von drei Spalten in einer schmaleren ist. Die Komponente reagiert auf ihren tatsächlichen Platz, nicht auf eine Viewport-Zahl, die damit vielleicht nichts zu tun hat.
Container-Query-Einheiten
Neben @container gibt es eine passende Menge von Längeneinheiten, die sich auf den Query-Container statt auf den Viewport beziehen: cqw, cqh, cqi, cqb, cqmin, cqmax (analog zu vw/vh/vmin/vmax, aber relativ zum Container). Das ist nützlich für fluide Typografie, die mit der eigenen Box der Komponente skalieren soll statt mit der ganzen Seite:
.card-title {
font-size: clamp(14px, 5cqi, 22px);
}
5cqi sind 5 % der Inline-Größe des Containers – der Titel wächst also mit, wenn die Karte breiter wird, begrenzt zwischen 14 px und 22 px, unabhängig davon, was die Viewport-Breite der restlichen Seite macht.
Ein häufiger Stolperstein: den eigenen Container abfragen
container-type etabliert Containment auf einem Element, und @container-Queries schauen auf den nächsten Vorfahren mit diesem Containment – ein Element kann sich nicht selbst abfragen:
/* This does NOT work as you might expect */
.card {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
/* .card can't query the container-type it declares on itself */
}
}
Die Lösung ist das Muster aus dem früheren Beispiel: container-type auf das Elternelement (.card-grid) setzen und es von den Kindern (.card) aus abfragen. Das bringt Leute zum Stolpern, weil es das Gegenteil dessen ist, wonach man instinktiv greift – man will, dass die Karte auf ihre eigene Größe reagiert, aber das Containment muss eine Ebene höher liegen.
Browser-Unterstützung und Fallback
Container Queries werden in aktuellem Chrome, Firefox, Safari und Edge solide unterstützt. Für alles, was auf wirklich alten Browsern laufen muss, lässt sich mit @supports ein Media-Query-Fallback bereitstellen:
.card {
flex-direction: column;
}
@media (min-width: 700px) {
.card {
flex-direction: row;
}
}
@supports (container-type: inline-size) {
@media (min-width: 700px) {
.card {
flex-direction: column; /* undo the media-query version */
}
}
.card-grid {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
flex-direction: row;
}
}
}
Fazit
Media Queries beschreiben den Viewport; Container Queries beschreiben den tatsächlich verfügbaren Platz der Komponente – fast immer genau das, wonach man von Anfang an fragen wollte. Wenn du jemals eine Komponente geschrieben hast, die oben auf der Seite richtig aussieht und falsch, sobald sie in einer Sidebar wiederverwendet wird, ist das genau das Problem, für das es Container Queries gibt.