Hilfe, die CSS Container Queries kommen: Alles was du jetzt wissen musst
CSS Container Queries sind ein neuer Standard für Weblayouts, welcher im September 2022 im Chrome-Browser (in Version 105 und 106) gelandet ist. Safari ist mit Version 16 [1] ebenfalls schon nachgezogen.
In diesem Artikel werde ich ausführen, wofür Container-Queries besonders gut geeignet sind, wie sie die Web-Entwicklung vereinfachen werden und wie du damit moderne, responsive Weblayouts erstellen kannst.
Was genau sind Container Queries?
Mit Container-Queries kannst du Komponenten einer Website so gestalten, dass sie sich je nach Größe des übergeordneten Elements (des Containers) anpassen. Dadurch kannst du vermeiden, dass sie je nach Breite des Bildschirms zu gedrängt oder zu breit aussehen.
Wozu sind CSS Container Queries gut?
Von Tabellen bis hin zu den Karten-Modulen kannst du CSS Container Queries so einsetzen, dass sie verschiedene Probleme lösen, ohne dass du dafür JavaScript benötigst.
Was ist das Problem bei CSS Media Queries?
Eine Webseite besteht üblicherweise aus verschiedenen Abschnitten und Komponenten, die wir mithilfe von CSS Media-Queries responsiv machen. Das Problem hierbei? Oft geht es beim responsiven Webdesign eigentlich nicht um den Viewport oder die Bildschirmgröße, sondern um die Breite des übergeordneten Containers.
Im Beispiel oben haben wir ein typisches, Karten-basiertes Layout, das es in zwei Varianten gibt:
- a) Die Karten werden übereinander angeordnet (linke Spalte)
- b) Die Karten werden nebeneinander angeordnet (rechte Spalte)
Schauen wir uns folgendes Beispiel an: Wir haben eine Kartenkomponente, die zu einer inneren horizontalen Anordnung hin wechseln soll, wenn genug Platz vorhanden ist.
Es gibt mehrere Möglichkeiten, dieses Layout ins CSS umzusetzen. Die häufigste ist die folgende: Wir erstellen eine Basiskomponente, und variieren diese je nach Breite des Bildschirms.
.c-article {
/* Basisversion, Karten sind übereinander angeordnet */
}
.c-article > * + * {
margin-top: 1rem;
}
/* Ab 46rem werden die Karten nebeneinander angeordnet */
@media (min-width: 46rem) {
.c-article--horizontal {
display: flex;
flex-wrap: wrap;
}
.c-article > * + * {
margin-top: 0;
}
.c-article__thumb {
margin-right: 1rem;
}
}
Beachte, dass wir die Klasse .c-article--horizontal
erstellt haben, um die horizontale Version der Komponente zu definieren. Diese kommt ins Spiel, sobald die Breite des Viewports größer als 46 rem ist. Besser wäre es aber, wenn die Komponente direkt auf die Breite ihres übergeordneten divs reagiert, nicht nur auf die Bildschirmgröße.
Nehmen wir an, wir wollen die (mobile) Standard-.c-card
auch auf Desktop (bzw. ab 46rem Bildschirmbreite) verwenden. Was passiert? Die Karten werden zu klobig, wie in der folgenden Abbildung zu sehen ist:
Dieses Problem bekommen wir Dank CSS Container Queries jetzt gelöst. Im Beispiel oben ist unser Wunschszenario, dass wenn der übergeordnete Container größer als 400 Pixel ist, sich das Layout der Karte zum horizontalen Stil hin ändern soll. Zunächst das Markup (HTML):
<div class="o-grid">
<div class="o-grid__item">
<article class="c-article">
<!-- content -->
</article>
</div>
<div class="o-grid__item">
<article class="c-article">
<!-- content -->
</article>
</div>
</div>
Das CSS mit der @container
Query wird so aussehen:
.o-grid__item {
container-type: inline-size;
}
.c-article {
/* The default style */
}
@container (min-width: 400px) {
.c-article {
/* The styles that will make the article horizontal**
** instead of a card style.. */
}
}
Wie genau kommen jetzt CSS Container Queries ins Spiel?
Mit CSS-Container-Abfragen können wir eine Komponente erstellen, die ihr inneres Layout je nach vorhandenem Platz automatisch anpasst. Im folgenden siehst du, wie ich mir das vorstelle.
Die violette Klammer stellt die übergeordnete Breite dar. Die Komponente passt sich automatisch an ihre Umgebung (an den übergeordneten Container) an, wenn diese größer wird.
Wie funktionieren CSS Container-Queries?
Der erste Schritt besteht darin, die Eigenschaft container-type
hinzuzufügen. Da sich eine Komponente auf der Grundlage ihrer übergeordneten Breite anpasst, müssen wir dem Browser mitteilen, dass nur der betroffene Bereich neu gezeichnet werden soll, nicht die gesamte Seite. Mit der Eigenschaft container-type
können wir dem Browser dies im Voraus mitteilen.
Der Wert inline-size
bedeutet, dass nur auf Änderungen der Breite des Elternteils reagiert werden soll.
<div class="o-grid">
<div class="o-grid__item">
<article class="c-article">
<!-- content -->
</article>
</div>
<div class="o-grid__item">
<article class="c-article">
<!-- content -->
</article>
</div>
<!-- other articles.. -->
</div>
.o-grid__item {
container-type: inline-size;
}
Dies ist der erste Schritt. Wir haben das Element .o-grid__item
als Containment Parent für das Element .c-article
definiert.
Im nächsten Schritt fügen wir die Stile hinzu, die wir für die Containerabfragen benötigen.
.o-grid__item {
container-type: inline-size;
}
@container (min-width: 400px) {
.c-article {
display: flex;
flex-wrap: wrap;
}
/* other CSS.. */
}
Der @container
ist das .o-grid__item
Element und min-width: 400px
ist die erforderliche Mindestbreite. Wir können sogar noch weiter gehen und weitere Stile hinzufügen. Hier ist ein Video, das zeigt, was man mit der Kartenkomponente erreichen kann:
Die Stile, die wir dort haben, sind:
- Der Standard, ein kartenähnliches Aussehen.
- Eine horizontale Karte mit einem kleinen Vorschaubild.
- Eine horizontale Karte mit einem großen Vorschaubild.
- Wenn das übergeordnete Element zu groß ist, wird der Stil hero-like, um anzuzeigen, dass es sich um einen vorgestellten Artikel handelt.
Sehen wir uns nun die Anwendungsfälle für CSS-Container-Abfragen an.
Wie sieht die Syntax bei Container Queries aus?
Um eine Komponente basierend auf ihrer übergeordneten Breite abzufragen, brauchen wir die Eigenschaft container-type
. Hier ein einfaches Beispiel:
.wrapper {
container-type: inline-size;
}
Damit können wir beginnen, eine Komponente abzufragen. Im folgenden Beispiel, wenn der Container der .card
Element hat eine Breite gleich 400px
oder größer ist, müssen wir einen bestimmten Stil hinzufügen.
@container (min-width: 400px) {
.card {
display: flex;
align-items: center;
}
}
Das funktioniert zwar, kann aber etwas unübersichtlich werden, wenn du mehrere Container hast. Um das zu vermeiden, ist es besser, einen Container zu benennen.
.wrapper {
container-type: inline-size;
container-name: card;
}
Jetzt können wir den Containernamen wie folgt an @container
anhängen:
@container card (min-width: 400px) {
.card {
display: flex;
align-items: center;
}
}
Gehen wir noch einmal auf das ursprüngliche Beispiel ein und sehen wir uns an, wie wir von Container-Abfragen profitieren können, um mehrere CSS-Klassen zu vermeiden.
.wrapper {
container-type: inline-size;
container-name: card;
}
.c-article {
/* Default stacked style */
}
@container card (min-width: 400px) {
/* Horizontal style. */
.c-article {
display: flex;
align-items: center;
}
}
Anwendungsfälle für CSS Container Queries
Container-Abfragen und CSS-Grid mit auto-fit
In einigen Fällen kann die Verwendung von auto-fit
im CSS-Grid zu unerwarteten Ergebnissen führen. Zum Beispiel wird eine Komponente zu breit und ihr Inhalt ist schwer zu lesen.
Um dir den Zusammenhang besser zu verdeutlichen, findest du hier eine Grafik, die den Unterschied zwischen auto-fill
und auto-fit
in CSS Grid zeigt.
Beachte, dass sich die Elemente bei der automatischen Anpassung mit auto-fit
so erweitert, dass sie den verfügbaren Platz ausfüllen. Im Falle von auto-fill
wachsen die Elemente des Grids jedoch nicht und es bleibt stattdessen ein freier Platz (das gepunktete Element ganz rechts oben).
Du fragst dich jetzt vielleicht, was das mit CSS-Container-Abfragen zu tun hat? Jedes Grid-Element ist ein Container. Wenn dieser Container mit auto-fit
erweitert wird, müssen wir die Komponente anpassen:
<div class="o-grid">
<div class="o-grid__item">
<article class="c-article"></article>
</div>
<div class="o-grid__item">
<article class="c-article"></article>
</div>
<div class="o-grid__item">
<article class="c-article"></article>
</div>
<div class="o-grid__item">
<article class="c-article"></article>
</div>
</div>
.o-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 1rem;
}
Wenn wir vier Elemente haben, sollte das Ergebnis in etwa so aussehen.
Das wird sich ändern, wenn die Anzahl der Artikel geringer ist. Je weniger Artikel wir haben, desto breiter werden sie. Der Grund dafür ist, dass auto-fit
verwendet wird. Mit vier oder drei Elementen nebeneinander sieht das noch gut aus, aber die letzten beiden Beispiele werden zu breit.
Wie wäre es, wenn sich jede Artikelkomponente basierend auf der Breite der übergeordneten Komponente ändert? Auf diese Weise können wir die Vorteile von auto-fit
nutzen. Dazu müssen wir folgendes tun:
Wenn die Breite des Grid-Elements größer als 400px ist, sollte der Artikel zum horizontalen Stil wechseln.
Und so setzen wir dies um:
.o-grid__item {
container-type: inline-size;
}
@container (min-width: 400px) {
.c-article {
display: flex;
flex-wrap: wrap;
}
}
Außerdem wollen wir den Artikel mit einem Hero-Abschnitt anzeigen, wenn er der einzige Artikel im Raster ist.
.o-grid__item {
container-type: inline-size;
}
@container (min-width: 700px) {
.c-article {
display: flex;
justify-content: center;
align-items: center;
min-height: 350px;
}
.card__thumb {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
}
Das war’s. Wir haben eine Komponente, die auf die Breite ihres übergeordneten Containers reagiert und zu jedem Kontext passt.
Sidebar und Content-Spalte
Desöfteren müssen wir eine Komponente so anpassen, dass sie auch in einem schmalen Container passt, wie z. B. in einer <aside>
.
Ein perfektes Beispiel dafür ist eine Newsletter-Box. Wenn die Breite gering ist, müssen die einzelnen Elemente vertikal angeordnet werden. Wenn hingegen genug Platz vorhanden ist, sollen sie horizontal mitwachsen.
Wie du in der Abbildung siehst, haben wir eine Newsletter-Box, die in zwei verschiedenen Kontexten angezeigt werden soll:
- Links in einer Sidebar
- Rechts in der Content-Spalte
Das CSS dafür sieht so aus:
.newsletter-wrapper {
container-type: inline-size;
}
/* The default, stacked version */
.newsletter {
/* CSS styles */
}
.newsletter__title {
font-size: 1rem;
}
.newsletter__desc {
display: none;
}
/* The horizontal version */
@container (min-width: 600px) {
.newsletter {
display: flex;
justify-content: space-between;
align-items: center;
}
.newsletter__title {
font-size: 1.5rem;
}
.newsletter__desc {
display: block;
}
}
Hier das Video mit dem Ergebnis.
Link zur Demo auf CodePen.
Paginierung
Paginierungen eignen sich ebenfalls sehr gut für die Verwendung von Container-Queries. Am Anfang haben wir die Schaltflächen „Zurück“ und „Weiter“. Je nach vorhandenem Platz können wir dann noch die einzelnen Unterseiten anzeigen, wie in folgender Abbildung zu sehen ist.
Um das Layout oben zu erhalten, verwenden wir folgendes CSS:
.wrapper {
container-type: inline-size;
}
@container (min-width: 250px) {
.pagination {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.pagination li:not(:last-child) {
margin-bottom: 0;
}
}
@container (min-width: 500px) {
.pagination {
justify-content: center;
}
.pagination__item:not(.btn) {
display: block;
}
.pagination__item.btn {
display: none;
}
}
Link zur Demo auf CodePen.
Nutzerprofile
Hier ein weiterer Fall, der sich sehr gut eignet, um in verschiedenen Kontexten eingesetzt zu werden. Das kleine Nutzerprofil links eignet sich für mobile Ansichtsgrößen oder Umgebungen wie in einer Sidebar. Die größere Ansicht kannst du hingegen zum Beispiel in mehrspaltigen Layouts einsetzen.
.p-card-wrapper {
container-type: inline-size;
}
.p-card {
/* Default styles */
}
@container (min-width: 450px) {
.meta {
display: flex;
justify-content: center;
gap: 2rem;
border-top: 1px solid #e8e8e8;
background-color: #f9f9f9;
padding: 1.5rem 1rem;
margin: 1rem -1rem -1rem;
}
/* and other styles */
}
So können wir ein und dieselbe Komponente in verschiedenen Kontexten einsetzen, ohne eine einzige Media-Query verwenden zu müssen.
Link zur Demo auf CodePen.
Kontaktformulare
Hier ein möglicher Anwendungsfallen für Kontaktformulare, in welchem sich das Layout dem übergeordneten Container anpasst.
.form-item {
container-type: inline-size;
}
.input-group {
@container (min-width: 350px) {
display: flex;
align-items: center;
gap: 1.5rem;
input {
flex: 1;
}
}
}
Link zur Demo auf CodePen.
Ist es möglich, Fallbacks für Container Queries einzubauen?
Ja, es ist möglich, Containter Query-Fallbacks einzurichten. Hier sind zwei tolle Artikel, die erklären, wie man das macht:
- Container Query Solutions with CSS Grid and Flexbox von Stephanie Eckles
- Container Queries are actually coming von Andy Bell
Quellen und weiterführende Links
3. Versuchslabor von Ahmad Shadeed
Danke für diesen tollen Artikel.
Hat mir sehr geholfen, diese Thematik zu verstehen.
Wäre schön, wenn es noch Infos und Erklärungen zu CSS Container Query Units (cqw, usw.) geben würde.
Klasse Artikel. Hat mir schon einmal geholfen!