CSS Container Queries mit @container
Mit CSS Container Queries kann ein eigener Breakpoint pro Komponente festgelegt werden. Die @container-Regel wird klassisches Responsive Design revolutionieren.
Das Abfragen der Viewport-Größe ist mit Media Queries seit langem möglich und zentraler Bestandteil des Responsive Web Design (RWD). Allerdings haben sich in der Weiterentwicklung von RWD und in der parallelen Entwicklung hin zu modularen Layouts neue Anforderungen ergeben. Der Platz, den eine einzelne Komponente zur Verfügung hat, ist heute meist relevanter als die Breite des gesamten Browserfensters. Die Web-Community wünschte sich eine entsprechende Technologie und hat jahrelang an Übergangslösungen gearbeitet. Mit der CSS-Rule @container
(sog. Container Queries) steht eine native CSS-Technik zur Verfügung.
Wann Media Queries an ihre Grenzen stoßen
Schauen wir uns zunächst ein Beispiel an, um zu verstehen worin der Vorteil von Container Queries liegt. Unser Beispiel-Layout beinhaltet eine typische Website-Komponente – die sog. Card – die aus Bildbereich und Inhalt besteht. Wenn die Card wenig Platz zur Verfügung hat, ist sie blau und das Bild soll über dem Inhalt stehen. Wenn mehr Raum verfügbar ist, soll das Bild nach links rutschen und die Card grün umgefärbt werden. Die Card besitzt also zwei verschiedene Zustände.
Wenn wir das Projekt mobile First aufbauen, stellt die blaue Card das Standardverhalten dar. Um bei einer Viewportbreite von 500 Pixeln auf die grüne Card umzuschalten, schreiben wir einen Media Query. Leider müssen wir ab 800 Pixeln das Verhalten widerrufen oder den Code umständlich zusammenfassen. Dadurch, dass das Verhalten von Seiten-Layout und Komponente im Code nicht sauber getrennt werden kann, entstehen sehr viele Kombinationsmöglichkeiten.
Das Problem: Redundanter Code für simple Anforderungen
Spätestens wenn weitere Layouts hinzukommen, müssen wir Sonderregeln schreiben, obwohl die Card keinen zusätzlichen Zustand einnimmt. Wie man sieht, soll die Card auf weiteren Unterseiten in der Desktopversion blau sein, da sie in der Seitenleiste bzw. dreispaltig dargestellt wird. Die einzelne Card hat somit nur wenig Raum zur Verfügung – vergleichbar mit der Ansicht auf kleinen Displays.
Da wir das Styling der Card bisher mit einem Media Query anhand der Viewportbreite festlegen, werden die falschen Stile angewandt. Das Ergebnis sähe ohne eine weitere Sonderlösung im Code wie folgt aus:
Die Lösung: Eigene Breakpoints pro Komponente
Wünschenswert ist eine Komponente die eigene Breakpoints besitzt und auf den verfügbaren Raum im Elternelement (dem Container) reagiert. In unserem Beispiel hätte die Komponente »Card« dann zwei Zustände und einen Breakpoint.
Syntax und Funktionsweise von @container
Mit CSS Container Queries kann ein eigener Breakpoint pro Komponente festgelegt werden. Die Komponente reagiert, sobald der Container (das Elternelement) eine bestimme Breite über- oder unterschreitet. Somit ist eine Trennung des Seitenlayouts vom Layout der Komponente möglich, was den Code deutlich vereinfacht.
Das folgende Beispiel zeigt die grundlegende Syntax mit @container
. Sobald die Card breiter als 500px wird, soll sie von Blau auf Grün umgefärbt werden. Detail zur Syntax findet ihr in der Spezifikation für CSS Container Queries.
.container {
container-type: inline-size;
}
.card {
background: lightblue;
}
@container (min-inline-size: 500px) {
.card {
background: lightgreen;
}
}
Bitte beachtet, dass .container
die Eigenschaft container-type: inline-size;
erhalten hat. .container
ist in diesem Beispiel das Elternelement, auf das reagiert werden soll.
Der Befehl container-type: inline-size;
informiert den Browser, dass der Container sich auf der Inline-Achse in der Größe verändern kann – also in unserem Beispiel in der Breite. Es gibt u.a. auch den Wert size
für die Kombination aus Block- und Inline-Size. Die Angaben basieren auf den Logical Properties von CSS und sind abhängig von der Leserichtung des Layouts. Auch der Media Query ist nicht mit min-width
formuliert (was allerdings funktionieren würde), sondern mit min-inline-size
.
Wenn kein Container Query aktiviert werden soll, bzw. wenn ihr den Container Query zurücksetzen möchtet, verwendet den Wert normal
. In Zukunft können wir wahrscheinlich, neben den hier gezeigten Size Queries, auch Style-Queries oder State-Queries verwenden.
Beispiel: Mehrspaltiges Layout mit CSS Container Queries
Schauen wir uns das zuvor beschriebene Beispiel vor dem Hintergrund von Container Queries noch einmal an. Der Grundaufbau im HTML folgt einem simplen Schema.
<main>
<div class="container">
<div class="card"> … </div>
</div>
<div class="container">
<div class="card"> … </div>
</div>
<div class="container">
<div class="card"> … </div>
</div>
<div class="container">
<div class="card"> … </div>
</div>
</main>
Das mehrspaltige Layout wird mit einem intrinsischen CSS Grid hergestellt, dass dem <main>
-Element zugewiesen wurde. Auffällig ist, dass der .container
im HTML-Code mehrmals vorkommt, obwohl wir mit CSS Grid ein Raster erstellen. Das hängt damit zusammen, dass die Rasterzellen nicht als Container dienen können.
main {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(500px, 100%), 1fr));
grid-template-rows: 1fr 1fr 1fr 1fr;
margin: 0;
}
Es folgt das Basis-Styling der Cards. Dieser Teil wird hier nur der Vollständigkeit halber abgebildet und ist für die Funktionsweise des Container Query-Layouts unerheblich.
.card {
--color:#257989;
--strokewidth: 2px;
color: var(--color);
border: var(--strokewidth) solid;
margin: 1em;
}
.card-header {
aspect-ratio: 20/9;
background: var(--color);
}
.card-content {
padding: 1em;
}
.card-btn {
padding: .8em 1.2em;
color: var(--color);
border: var(--strokewidth) solid var(--color);
background: none;
font-size: 1em;
}
Der spannende Teil folgt jetzt. Die Klasse .container
erhält mittels container-type
die Angabe, dass die inline-size
(hier Breite) ausschlaggebend für die Anpassung der Card ist.
Mit @container (min-inline-size: 560px) { }
definieren wir eine Regel, die immer dann gilt, wenn der Container (hier .container
) größer als 560 Pixel auf der Inline-Achse ist. Sobald das der Fall ist, definieren wir die CSS-Variable für die Farbe neu und ändern das Layout mit Flexbox.
.container {
container-type: inline-size;
}
@container (min-inline-size: 560px) {
.card {
--color: #8cb11c;
display: flex;
}
.card-header {
flex: 1 0 40%;
}
}
Einheiten für CSS Container Queries
Die Spezifikation rund um Container Queries führt neben @container
auch zahlreiche neue Einheiten ein. Wer mit den Einheiten vw
, vh
etc. für Viewports bereits vertraut ist, wird hier wenig Startschwierigkeiten haben.
Die Container Query Units lauten:
cqw
=width
des Container Queriescqh
=height
des Container Queriescqi
=inline-size
des Container Queriescqb
=block-size
des Container Queriescqmin
= Der kleinere Wert voncqi
undcqb
cqmax
= Der größere Wert voncqi
undcqb
Beispiel: Schriftgröße abhängig vom Container ändern
Schauen wir uns ein Beispiel an, um besser zu verstehen welche Möglichkeiten Container Query Units bieten. Mit der CSS Eigenschaft clamp()
definieren wir eine variable Schriftgröße mit einer Mindest- und einer Maximalgröße. Die mittlere Angabe in clamp()
legt den Orientierungspunkt fest. Mit 5cqi
gestalten wir eine dynamische Schriftgröße auf Grundlage von 5% der Inline-Größe des Containers.
<div class="container">
<div class="card"> … </div>
</div>
.container {
container-type: inline-size;
}
.card {
font-size: clamp(1em, 5cqi, 2em);
}
Wir haben zu diesem Beispiel eine interaktive Demo erstellt. Beispiel in neuem Tab öffnen
Container Query Names – Benannte Container
Elemente reagieren normalerweise auf Container, die in der HTML-Struktur direkte Elternelemente sind. Allerdings ist es möglich, mit Hilfe benannter Container auf nicht-direkte Elternelemente Bezug zu nehmen. Somit ist es z.B. auch möglich, verschiedene Eigenschaften unterschiedlicher Elternelemente für die Anpassung der Komponente zu verwenden.
Um einen benannten Container zu erzeugen, wird die Eigenschaft container-name
verwendet. Innerhalb des Container Query wird der Name dann vor der Abfrage der Query-Eigenschaft angegeben. Es sind auch mehrere Namen möglich.
.container {
container-type: inline-size;
container-name: mein-container; /* Der Name kann frei vergeben werden */
}
/* Der Container »mein-container« wird angesprochen */
@container mein-container (min-inline-size: 500px) {
…
}
Beispiel: Eigenschaften von verschiedenen Containern abfragen
Schauen wir uns an, wie Container Names in der Praxis funktionieren können. Das folgende Beispiel zeigt eine Komponente die im HTML zwei Mal verschachtelt wurde.
<div class="outer">
<div class="inner">
<div class="component"> … </div>
</div>
</div>
Beide Elternelemente werden zu Containern umfunktioniert – allerdings mit unterschiedlichen Namen und Typen. Der Container outer
reagiert dank container-type: size;
auf die Block-Size (= Höhe), der Container inner
reagiert, wie die zuvor gezeigten Beispiele, auf die Breite.
.outer {
container-type: size;
container-name: outer;
}
.inner {
container-type: inline-size;
container-name: inner;
}
Nun folgen die Container Queries. Die Komponente soll sich verändern, wenn outer
mindestens 50% Block-Size (hier Höhe) des Browserfensters eingenommen hat (50vb
). Zusätzlich soll sich die Komponente verändern, wenn inner
80% der Inline-Size (hier Breite) von outer
eingenommen hat (80cqi
).
@container outer (min-block-size: 50vb) {
.component {
background: grey;
color: white;
}
}
@container inner (min-inline-size: 80cqi) {
.component {
border: 10px solid black;
}
}
Wir haben eine interaktive Demo erstellt, anhand der ihr das Verhalten ausprobieren könnt. Beispiel in neuem Tab öffnen
Die Kurzschreibweise für einen Container Query
Es existiert auch eine Kurzschreibweise für Container Queries, die die Eigenschaften container-name
und container-type
zusammenfasst. dabei werden die Werte mit Slash getrennt in eine Zeile geschrieben. Zuerst der Name, dann der Typ.
/* Lange Schreibweise */
.container {
container-type: inline-size;
container-name: mein-container;
}
/* Kurze Schreibweise */
.container {
container: mein-container / inline-size;
}
Browser Support
Den detaillierten Browser-Support für dieses Feature könnt ihr auf caniuse.com einsehen.
Fazit
Container Queries werden, ähnlich wie seinerzeit Media Queries, die Art wie wir Weblayouts und Komponenten entwickeln grundlegend verändern. Das Bedürfnis nach Lösungen dieser Art ist sehr groß – was auch Entwicklungen wie Intrinsic Design gezeigt haben. In Kombination mit CSS :has() und @layers stehen uns Möglichkeiten zur Verfügung, die komplexe und robuste Layouts mit wenig Code ermöglichen.
Wo genau liegt denn der Unterschied zwischen cqw und cqi. Beide Angaben beziehen sich doch auf die (gleiche?) länge?
Das sind zwei verschiedene Schreibweisen, die in unserer Leserichtung (links oben nach rechts unten) identisch sind. cqi bezeichnet die Inline-Achse. Diese Schreibweise ist moderner und robuster und Teil der sog. Logical Properties von CSS.
Bis vor den Container Queries war es so, dass man die Schriftgröße und auch die Breite von Containern und deren margin und padding in der Einheit rem angeben sollte, damit sich diese Elemente an die vom Nutzer im Browser eingestellte Schriftgröße anpassen, aber mit den Container Query Units funktioniert das nicht mehr so.
Wenn die Standardschriftgröße im Browser von 16 auf 18 erhöht wird, dann ändert sich zwar die Breite eines Containers entsprechend, wenn die Breite in rem angegeben ist, aber die Schriftgröße erhöht sich nach dem, was ich bisher gesehen und ausprobiert habe, nicht in dem Maß, wie es eigentlich gewünscht ist.
Sehr gut erklärt, jetzt nur noch daraf warten, das Firefox unter Android die CQ endlich auch unterstützt
Wie findet man raus, wann das soweit ist?
Gruß Kolja
Soweit ich informiert bin, sind CQ sind Kürze im nächsten Firefox Nightly und dann ab Firefox 109 in der normalen Version mit dabei.
Vielen Dank für die tolle Einführung in die Container-Queries! Top – wie immer.
Folgende Kleinigkeit ist mir noch aufgefallen:
Unter „Benannte Container“ schreibst du im CSS Beispiel „@container mein-name“ – denke, hier sollte es „@container mein-container“ heißen. Oder?
Vielen Dank für deinen Kommentar! In der tat war der Container-Name abweichend zwischen Kommentar und @rule. Ich habe es korrigiert.
Wieder mal ein sehr toller Artikel von dir, Jonas!