Container Queries und responsive Komponenten
Media Queries reagieren normalerweise auf die Größe des Viewports. Eine
Komponente weiß dadurch jedoch nicht, wie viel Platz ihr innerhalb des
tatsächlichen Layoutbereichs zur Verfügung steht.
Container Queries ermöglichen es, die Darstellung einer Komponente von der
Größe ihres umgebenden Containers abhängig zu machen. Dieselbe Komponente
kann dadurch beispielsweise in einer schmalen Seitenleiste einspaltig und
im breiten Hauptbereich mehrspaltig erscheinen.
Media Query und Container Query im Vergleich
| Technik |
Reagiert auf |
Typischer Einsatz |
@media |
Viewport oder Ausgabegerät |
Gesamtes Seitenlayout und globale Navigation |
@container |
Größe oder Eigenschaften eines Containers |
Wiederverwendbare Komponenten |
@media (min-width: 60rem) {
.page-layout {
grid-template-columns: 18rem 1fr;
}
}
@container (min-width: 35rem) {
.card {
grid-template-columns: 12rem 1fr;
}
}
Die Media Query verändert das übergeordnete Seitenlayout. Die Container
Query verändert die Karte entsprechend dem Platz, den ihr eigener
Container bereitstellt.
Einen Größencontainer definieren
.card-wrapper {
container-type: inline-size;
}
Mit container-type: inline-size wird das Element zu einem
Abfragecontainer. Container Queries können anschließend auf seine Größe
entlang der Inline-Achse reagieren.
Bei horizontal geschriebenem Text entspricht die Inline-Achse
normalerweise der Breite.
Die Container Query prüft nicht die Größe der Komponente selbst, sondern
die Größe eines geeigneten übergeordneten Abfragecontainers.
Die erste Container Query
.card-wrapper {
container-type: inline-size;
}
.card {
display: grid;
gap: 1rem;
}
@container (min-width: 30rem) {
.card {
grid-template-columns: 10rem 1fr;
align-items: start;
}
}
Solange der Container schmaler als 30rem ist, bleiben Bild
und Inhalt untereinander. Ab einer Containerbreite von
30rem werden sie nebeneinander angeordnet.
container-type
| Wert |
Bedeutung |
normal |
Das Element ist kein Größencontainer. Style Queries können
abhängig von der Unterstützung trotzdem möglich sein.
|
inline-size |
Größenabfragen beziehen sich auf die Inline-Abmessung des
Containers.
|
size |
Größenabfragen können sich auf die Inline- und Block-Abmessung
beziehen.
|
Für die meisten responsiven Komponenten ist
inline-size die geeignete Wahl. Eine Abhängigkeit von der
Höhe ist wesentlich seltener erforderlich.
Einen Container benennen
.main-content {
container-name: content;
container-type: inline-size;
}
Mit container-name erhält ein Abfragecontainer einen
eindeutigen Namen. Eine Container Query kann dadurch gezielt auf diesen
Container verweisen.
@container content (min-width: 45rem) {
.article-card {
grid-template-columns: 14rem 1fr;
}
}
Die Kurzschreibweise container
.main-content {
container: content / inline-size;
}
Die Eigenschaft container kombiniert den Namen und den Typ
eines Abfragecontainers.
container: container-name / container-type;
Der Schrägstrich trennt den Namen des Containers von seinem
container-type.
Unbenannte und benannte Container Queries
@container (min-width: 30rem) {
.card {
grid-template-columns: 10rem 1fr;
}
}
Ohne Namen sucht der Browser beim Element nach dem nächsten geeigneten
übergeordneten Abfragecontainer.
@container sidebar (min-width: 20rem) {
.card {
padding: 1.5rem;
}
}
Mit einem Namen wird gezielt der passende übergeordnete Container
verwendet.
Small Device First bei Container Queries
.product-card {
display: grid;
gap: 1rem;
padding: 1rem;
}
.product-card img {
width: 100%;
height: auto;
}
@container (min-width: 28rem) {
.product-card {
grid-template-columns: minmax(9rem, 35%) 1fr;
align-items: start;
}
}
@container (min-width: 50rem) {
.product-card {
grid-template-columns: minmax(14rem, 40%) 1fr;
padding: 2rem;
}
}
Die Grunddarstellung ist für schmale Container geeignet. Zusätzliche
Layoutmöglichkeiten werden erst aktiviert, wenn der Container
ausreichend Platz bietet.
Bereichsabfragen verwenden
@container (30rem <= width < 50rem) {
.card {
grid-template-columns: 12rem 1fr;
}
}
Mit der Bereichssyntax kann eine Darstellung auf einen bestimmten
Größenbereich begrenzt werden. In diesem Beispiel gilt die Regel ab
30rem, aber noch nicht ab 50rem.
@container (width >= 50rem) {
.card {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
Mehrere Bedingungen verbinden
@container (min-width: 35rem) and (max-width: 55rem) {
.card {
grid-template-columns: 12rem 1fr;
}
}
Bedingungen können wie bei Media Queries mit and
miteinander verbunden werden.
@container (width < 25rem) or (width > 60rem) {
.card-title {
font-weight: 800;
}
}
Mit or genügt eine der angegebenen Bedingungen. Durch
Klammern lassen sich komplexere Abfragen eindeutig strukturieren.
Container-relative Längeneinheiten
| Einheit |
Bezug |
cqw |
1 % der Breite des Abfragecontainers |
cqh |
1 % der Höhe des Abfragecontainers |
cqi |
1 % der Inline-Abmessung des Abfragecontainers |
cqb |
1 % der Block-Abmessung des Abfragecontainers |
cqmin |
Der kleinere Wert von cqi und cqb |
cqmax |
Der größere Wert von cqi und cqb |
Fließende Größen innerhalb einer Komponente
.card-wrapper {
container-type: inline-size;
}
.card {
padding: clamp(1rem, 4cqi, 2rem);
}
.card-title {
font-size: clamp(1.25rem, 5cqi, 2.25rem);
}
Die Abstände und die Überschrift reagieren auf die Inline-Größe des
Containers. Mit clamp() werden gleichzeitig sinnvolle
Mindest- und Höchstwerte festgelegt.
Container-relative Einheiten sollten für Textgrößen nicht ohne Begrenzung
verwendet werden. Andernfalls kann der Text zu klein oder zu groß werden.
Eine Komponente in unterschiedlichen Bereichen
.page-layout {
display: grid;
gap: 2rem;
}
.card-area {
container-type: inline-size;
}
@media (min-width: 64rem) {
.page-layout {
grid-template-columns: minmax(0, 2fr) minmax(16rem, 1fr);
}
}
@container (min-width: 35rem) {
.news-card {
display: grid;
grid-template-columns: 12rem 1fr;
gap: 1.5rem;
}
}
Im breiten Hauptbereich kann der Container die erforderlichen
35rem erreichen. In der schmaleren Seitenleiste bleibt
dieselbe Karte dagegen einspaltig.
Die Komponente benötigt keine Kenntnis darüber, ob sie sich im
Hauptbereich, in einer Seitenleiste oder in einem anderen Layout befindet.
Vollständige HTML-Struktur einer responsiven Karte
<div class="card-wrapper">
<article class="course-card">
<img
class="course-card-image"
src="images/css-grid.jpg"
alt="CSS-Grid-Beispiel auf einem Bildschirm"
>
<div class="course-card-content">
<p class="course-card-category">Webdesign</p>
<h3 class="course-card-title">
Responsive Layouts mit CSS Grid
</h3>
<p>
Das Beispiel zeigt einen flexiblen Seitenaufbau für unterschiedliche
Container- und Viewportgrößen.
</p>
<a href="css-grid.php">Beispiel ansehen</a>
</div>
</article>
</div>
Vollständiges CSS der responsiven Karte
.card-wrapper {
container: course-card / inline-size;
}
.course-card {
display: grid;
gap: 1rem;
height: 100%;
padding: 1rem;
background-color: #ffffff;
border: 1px solid #ced4da;
border-radius: 0.75rem;
box-shadow: 0 0.25rem 1rem rgb(0 0 0 / 0.08);
}
.course-card-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 0.5rem;
}
.course-card-content {
display: grid;
align-content: start;
gap: 0.75rem;
min-width: 0;
}
.course-card-category,
.course-card-title,
.course-card-content p {
margin: 0;
}
.course-card-category {
color: #6f42c1;
font-weight: 700;
}
.course-card-title {
font-size: clamp(1.25rem, 5cqi, 2rem);
line-height: 1.2;
overflow-wrap: break-word;
text-wrap: balance;
}
.course-card-content a {
justify-self: start;
padding: 0.625rem 1rem;
color: #ffffff;
background-color: #6f42c1;
border-radius: 0.375rem;
text-decoration: none;
}
.course-card-content a:hover {
background-color: #59359a;
}
.course-card-content a:focus-visible {
outline: 3px solid #fd7e14;
outline-offset: 3px;
}
@container course-card (min-width: 32rem) {
.course-card {
grid-template-columns: minmax(10rem, 40%) 1fr;
align-items: start;
padding: clamp(1rem, 4cqi, 2rem);
}
.course-card-image {
height: 100%;
min-height: 12rem;
aspect-ratio: auto;
}
}
@container course-card (min-width: 52rem) {
.course-card {
grid-template-columns: minmax(16rem, 35%) 1fr;
}
.course-card-content {
align-self: center;
}
}
Verschachtelte Container
.dashboard {
container: dashboard / inline-size;
}
.widget-area {
container: widget / inline-size;
}
@container dashboard (min-width: 60rem) {
.dashboard-layout {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@container widget (min-width: 25rem) {
.statistic-card {
grid-template-columns: auto 1fr;
}
}
Container können verschachtelt werden. Das Dashboard reagiert auf seinen
verfügbaren Platz, während die Statistik-Karte unabhängig davon auf die
Breite ihres eigenen Widget-Bereichs reagiert.
Aussagekräftige Containernamen helfen dabei, bei verschachtelten
Komponenten den richtigen Bezug eindeutig festzulegen.
Warum ein Element nicht sich selbst abfragen kann
/* Nicht wie erwartet */
.card {
container-type: inline-size;
}
@container (min-width: 30rem) {
.card {
grid-template-columns: 10rem 1fr;
}
}
Die Regel innerhalb einer Container Query bezieht sich auf einen
übergeordneten Container. Die Karte kann nicht gleichzeitig der
abgefragte Container und das Element sein, dessen eigene Gestaltung von
dieser Abfrage abhängt.
/* Geeignete Struktur */
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 30rem) {
.card {
grid-template-columns: 10rem 1fr;
}
}
Container benötigen eine verfügbare Breite
.cards-grid {
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1.5rem;
}
.card-wrapper {
min-width: 0;
container-type: inline-size;
}
Das äußere Grid bestimmt, wie viel Platz jeder Karte zur Verfügung steht.
Der Wrapper misst diesen Platz und stellt ihn für die Container Query
bereit.
min-width: 0 verhindert, dass lange Inhalte ein Grid-Element
über seine vorgesehene Spaltenbreite hinaus vergrößern.
Media Queries und Container Queries kombinieren
.page-layout {
display: grid;
gap: 1.5rem;
}
.component-area {
container-type: inline-size;
}
@media (min-width: 60rem) {
.page-layout {
grid-template-columns: 16rem minmax(0, 1fr);
}
}
@media (prefers-reduced-motion: no-preference) {
.card {
transition:
transform 200ms ease,
box-shadow 200ms ease;
}
.card:hover {
transform: translateY(-0.25rem);
}
}
@container (min-width: 35rem) {
.card {
grid-template-columns: 12rem 1fr;
}
}
Media Queries bleiben für globale Layoutänderungen und
Benutzereinstellungen wichtig. Container Queries ergänzen sie bei
komponentenbezogenen Layoutänderungen.
Progressive Verbesserung mit @supports
.card {
display: grid;
gap: 1rem;
}
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 32rem) {
.card {
grid-template-columns: 12rem 1fr;
}
}
}
Die Grunddarstellung funktioniert auch ohne Container Query. Unterstützt
der Browser die Technik, wird die erweiterte Darstellung aktiviert.
Die Basisversion einer Komponente sollte auch dann verständlich und
benutzbar bleiben, wenn eine neuere CSS-Funktion nicht verfügbar ist.
Style Queries mit Custom Properties
.card-area {
--card-theme: dark;
container-name: card-theme;
}
@container card-theme style(--card-theme: dark) {
.card {
color: #ffffff;
background-color: #212529;
}
}
Style Queries können die Darstellung von Eigenschaften eines
übergeordneten Containers abhängig machen. Besonders nützlich ist die
Abfrage von Custom Properties, über die eine Komponente beispielsweise
ein Darstellungsdesign erhalten kann.
Style Queries sind neuer als Größenabfragen. Vor dem produktiven Einsatz
sollte die benötigte Browserunterstützung geprüft und eine funktionierende
Grunddarstellung vorgesehen werden.
Container Queries mit JavaScript prüfen
if (CSS.supports("container-type: inline-size")) {
document.documentElement.classList.add("supports-container-queries");
}
In den meisten Fällen genügt eine CSS-Abfrage mit
@supports. JavaScript ist nur erforderlich, wenn die
Unterstützung auch die Programmlogik beeinflusst.
Für reine Darstellungsänderungen sollte CSS verwendet werden. Eine
Größenüberwachung mit JavaScript ist für normale Container-Query-Layouts
nicht notwendig.
Häufige Fehler
-
Für das übergeordnete Element wurde kein
container-type festgelegt.
-
Die Komponente versucht, ihre eigene Größe statt die Größe eines
übergeordneten Containers abzufragen.
-
Eine unbenannte Container Query verwendet unbeabsichtigt den falschen
übergeordneten Container.
-
Container Queries werden für globale Seitenänderungen verwendet, obwohl
eine Media Query verständlicher wäre.
-
Breakpoints werden von bekannten Gerätebreiten übernommen, ohne den
tatsächlichen Platzbedarf der Komponente zu berücksichtigen.
-
Es wird ausschließlich eine breite Darstellung definiert und keine
robuste Grunddarstellung für schmale Container vorgesehen.
-
Container-relative Schriftgrößen werden ohne Mindest- und Höchstwerte
verwendet.
-
Lange Inhalte verhindern, dass ein Grid- oder Flex-Element ausreichend
schrumpfen kann.
-
Bei verschachtelten Containern fehlen eindeutige Namen.
-
Die Komponente wird nur im Hauptbereich, aber nicht in einer schmalen
Seitenleiste oder einem kleinen Grid-Element geprüft.
-
Neue Techniken werden ohne funktionierende Grunddarstellung eingesetzt.
Zusammenfassung
-
Media Queries reagieren normalerweise auf den Viewport.
-
Container Queries reagieren auf den verfügbaren Platz eines
übergeordneten Containers.
-
Mit
container-type: inline-size wird ein Element zum
Größencontainer für die Inline-Achse.
-
container-name weist einem Container einen eindeutigen
Namen zu.
-
Die Kurzschreibweise
container kombiniert Namen und Typ.
-
Eine benannte Abfrage beginnt beispielsweise mit
@container content (...).
-
Container-Breakpoints sollten sich am tatsächlichen Platzbedarf der
Komponente und nicht an bestimmten Geräten orientieren.
-
cqw, cqh, cqi und
cqb beziehen sich auf Abmessungen eines
Abfragecontainers.
-
clamp() kann container-relative Größen sinnvoll
begrenzen.
-
Verschachtelte und benannte Container ermöglichen unabhängig
reagierende Komponenten.
-
Media Queries und Container Queries ergänzen einander.
-
Eine robuste Grunddarstellung bleibt auch ohne Unterstützung neuerer
CSS-Techniken nutzbar.
Container Queries ermöglichen wirklich wiederverwendbare Komponenten.
Eine Karte, ein Formular oder ein Teaser passt sich nicht nur an den
Viewport, sondern an den Platz an, der am jeweiligen Einsatzort
tatsächlich verfügbar ist.