mitmario.dev

Container Queries

CSS Im Browser 4 Min Lesezeit 3 BeispieleLektion 5 von 7

Eine Media Query kennt nur das Fenster. Für eine Seite reicht das. Für eine Komponente nicht, denn dieselbe Karte kann in einer breiten Spalte stehen und in einer schmalen, und das Fenster ist für beide dasselbe.

Eine Karte, zwei Spalten

Mit einer Media Query geht das nicht
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 24px;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
  background-color: #f4f4f5;
}

.layout {
  display: grid;
  grid-template-columns: 3fr 1fr;
  gap: 24px;
}

.projekt {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  border-radius: 12px;
  background-color: #ffffff;
}

.projekt img {
  flex-shrink: 0;
  width: 110px;
  height: 110px;
  border-radius: 8px;
  object-fit: cover;
}

.projekt h3 {
  margin: 0 0 4px 0;
  font-size: 18px;
}

.projekt p {
  margin: 0;
  color: #52525b;
}

/* Die Media Query kennt nur das Fenster. Das ist
   900px breit und damit weit über der Schwelle, also
   schaltet sie beide Karten um, auch die in der
   schmalen Spalte, die nur gut 200px hat. */
@media (min-width: 32rem) {
  .projekt {
    flex-direction: row;
  }
}

Dieselbe Projektkarte steht zweimal auf der Seite, einmal in einer breiten Spalte und einmal in einer schmalen Seitenspalte. In der breiten sollen Bild und Text nebeneinander stehen, in der schmalen untereinander.

Mit einer Media Query geht das nicht. Sie kennt nur eine Zahl, die Breite des Fensters, und die ist für beide Karten dieselbe. Schaltet sie um, schaltet sie beide um, und in der schmalen Spalte wird es gequetscht.

Behelfen kann man sich mit einer zweiten Klasse, .projekt--schmal, die an der Karte in der Seitenspalte hängt. Dann muss aber jeder, der die Karte irgendwo einbaut, wissen, wie breit die Stelle ist, an die er sie setzt. Das ist Wissen, das eine Komponente nicht haben sollte, und bei der ersten Umgestaltung geht es verloren.

container-type und @container

Dieselbe Karte, zwei Breiten
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 24px;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
  background-color: #f4f4f5;
}

.layout {
  display: grid;
  grid-template-columns: 3fr 1fr;
  gap: 24px;
}

/* Diese eine Zeile macht die Spalte abfragbar. */
.spalte {
  container-type: inline-size;
}

/* Die Grundform gilt für wenig Platz: untereinander. */
.projekt {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  border-radius: 12px;
  background-color: #ffffff;
}

.projekt img {
  flex-shrink: 0;
  width: 110px;
  height: 110px;
  border-radius: 8px;
  object-fit: cover;
}

.projekt h3 {
  margin: 0 0 4px 0;
  font-size: 18px;
}

.projekt p {
  margin: 0;
  color: #52525b;
}

/* Und ab 24rem Containerbreite nebeneinander. Beide
   Karten lesen dieselbe Regel, ihre Container sind
   nur verschieden breit. */
@container (min-width: 24rem) {
  .projekt {
    flex-direction: row;
  }
}

Erstens macht container-type: inline-size am Elternteil diesen abfragbar.

.spalte { container-type: inline-size; }

Zweitens fragt @container in der Komponente danach.

@container (min-width: 24rem) { .projekt { flex-direction: row; } }

Geprüft wird die Bedingung gegen den nächsten abfragbaren Vorfahren, nicht gegen das Fenster. Beide Karten im Beispiel lesen dieselbe Regel. Ihre Container sind nur verschieden breit, und deshalb verhalten sie sich verschieden.

Damit weiß die Karte selbst, wie sie sich verhalten soll, und die Seite muss ihr nichts mitgeben. Du kannst sie in ein dreispaltiges Raster legen, in eine Seitenspalte oder in ein Modal, und sie stimmt jedes Mal.

Die beiden Beispiele leben davon, dass die breite Spalte auch wirklich breit ist. In der schmalen Spalte ist dafür kein Platz, dort stapeln beide Karten. Im Vollbild steht die Seite von allein auf den 900 Pixeln, für die die Beispiele gebaut sind, und dort zeigt sich der Unterschied.

Mit dem Inspector siehst du dort den ganzen Vorgang auf einen Blick. Die Karte in der breiten Spalte misst rund 621 × 142, und unten im Streifen steht flex-direction: row. Die Karte in der schmalen misst rund 207 × 233, mit flex-direction: column. Dieselbe Klasse, dieselbe Regel, zwei verschiedene Werte, und nichts unterscheidet die beiden außer dem Platz, an dem sie stehen. In der schmalen Spalte neben dem Editor steht an beiden column, an der breiten bei rund 231 Pixeln Breite, denn sie liegt dort selbst unter der Schwelle von 24rem.

Sind mehrere abfragbare Container verschachtelt, kannst du sie benennen. container-name: karte steht dann am Elternteil und @container karte (min-width: 24rem) in der Abfrage. Für beides zusammen gibt es das Shorthand container: karte / inline-size.

Warum inline-size und nicht size

Für Größenabfragen kennt container-type drei Werte, und gebraucht wird fast immer derselbe.

  • inline-size macht die Breite abfragbar. Das ist der Normalfall.
  • size macht Breite und Höhe abfragbar.
  • normal ist der Standard, also nicht abfragbar.

size hat einen Haken. Der Browser soll eine Größe abfragen, bevor er den Inhalt kennt, und dafür muss er die Größe vom Inhalt unabhängig machen. Das nennt sich Containment. Bei size heißt es, dass der Container seine eigene Inhaltshöhe nicht mehr kennt. Ein Kasten, der so hoch sein soll wie sein Text, klappt dann auf null zusammen.

Bei inline-size gilt das nur für die Breite, und die kommt sowieso von außen. Deshalb nehme ich praktisch immer diesen Wert.

Container-Einheiten

Container-Einheiten
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 16px;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
  background-color: #f4f4f5;
}

.layout {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 16px;
  margin-bottom: 16px;
}

.spalte {
  container-type: inline-size;
}

.kasten {
  display: grid;
  place-items: center;
  min-height: 100px;
  border-radius: 12px;
  background-color: #2563eb;
}

.kasten span {
  font-size: 5cqi;
  font-weight: 700;
  color: #ffffff;
}

.hinweis {
  max-width: 60ch;
  color: #52525b;
}

Zu den Abfragen gehören eigene Einheiten. Sie beziehen sich auf den Container statt auf den Viewport.

  • cqi ist ein Prozent der Inline-Breite des Containers, und das ist die, die man meistens braucht.
  • cqw und cqh sind Breite und Höhe, cqmin und cqmax die kleinere und größere von beiden.

font-size: 5cqi heißt fünf Prozent der Containerbreite. Im Beispiel tragen beide Kästen dieselbe Regel, und die Schrift links ist doppelt so groß, weil die Spalte doppelt so breit ist. Mit 5vw wären beide gleich groß, denn es gibt nur ein Fenster.

Zusammen mit clamp() aus Lektion 5.3 bekommst du eine Schriftgröße, die sich nach dem tatsächlich verfügbaren Platz richtet, etwa mit font-size: clamp(1rem, 5cqi, 2rem).

In der Aufgabe steht eine Newsletter-Karte zweimal da, in einer breiten und einer schmalen Spalte, und beide stapeln ihren Inhalt.

Zum Mitnehmen

Eine Media Query fragt den Viewport. Eine Container Query fragt den Platz, den die Komponente tatsächlich hat. Dieselbe Karte in einer breiten Spalte und in einer schmalen Seitenspalte hat denselben Viewport und zwei völlig verschiedene Breiten.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.