7 Min. Lesezeit

CSS Container Queries – Responsive Komponenten

CSS Container Queries ermöglichen responsive Komponenten, die auf ihre eigene Größe reagieren statt auf den Viewport. Inkl. @container Syntax und Beispiele.

css

Seit Jahren kämpfen wir mit dem gleichen Problem: Media Queries reagieren nur auf die Viewport-Größe. Aber was, wenn eine Komponente in der Sidebar anders aussehen soll als im Hauptbereich – obwohl der Viewport gleich bleibt? Mit Container Queries können Komponenten endlich auf ihre eigene Größe reagieren.

Das Problem mit Media Queries

/* Klassische Media Query – reagiert auf Viewport */
@media (min-width: 768px) {
  .card {
    display: flex;
    flex-direction: row;
  }
}

Das funktioniert, solange die Karte immer die volle Breite nutzt. Aber:

┌─────────────────────────────────────────────────────────────┐
│ Viewport: 1200px                                            │
├───────────────────────────────────┬─────────────────────────┤
│                                   │                         │
│         Hauptbereich              │       Sidebar           │
│            800px                  │        300px            │
│                                   │                         │
│  ┌─────────────────────────────┐  │  ┌───────────────────┐  │
│  │      .card (horizontal)     │  │  │   .card (???)     │  │
│  │  ┌──────┐                   │  │  │                   │  │
│  │  │ Bild │  Titel + Text     │  │  │  Sollte vertikal  │  │
│  │  └──────┘                   │  │  │  sein, aber ist   │  │
│  │                             │  │  │  horizontal!      │  │
│  └─────────────────────────────┘  │  └───────────────────┘  │
│                                   │                         │
└───────────────────────────────────┴─────────────────────────┘

Die Media Query sagt: “Viewport > 768px → horizontal”. Aber die Sidebar-Karte hat nur 300px Platz!

Container Queries: Die Lösung

/* 1. Container definieren */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* 2. Auf Container-Größe reagieren */
@container card (min-width: 400px) {
  .card {
    display: flex;
    flex-direction: row;
  }
}

@container card (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}
<div class="card-container">
  <article class="card">
    <img src="bild.jpg" alt="">
    <div class="card-content">
      <h2>Titel</h2>
      <p>Beschreibung...</p>
    </div>
  </article>
</div>

Jetzt reagiert die Karte auf ihren Container, nicht den Viewport:

┌─────────────────────────────────────────────────────────────┐
│ Viewport: 1200px                                            │
├───────────────────────────────────┬─────────────────────────┤
│                                   │                         │
│         Hauptbereich              │       Sidebar           │
│     Container: 800px              │   Container: 300px      │
│                                   │                         │
│  ┌─────────────────────────────┐  │  ┌───────────────────┐  │
│  │   .card (horizontal ✓)      │  │  │ .card (vertikal ✓)│  │
│  │  ┌──────┐                   │  │  │  ┌─────────────┐  │  │
│  │  │ Bild │  Titel + Text     │  │  │  │    Bild     │  │  │
│  │  └──────┘                   │  │  │  └─────────────┘  │  │
│  │                             │  │  │  Titel + Text     │  │
│  └─────────────────────────────┘  │  └───────────────────┘  │
│                                   │                         │
└───────────────────────────────────┴─────────────────────────┘

Container-Typen

/* Nur Inline-Größe (Breite in horizontalen Sprachen) */
.container {
  container-type: inline-size;
}

/* Beide Achsen (Breite und Höhe) */
.container {
  container-type: size;
}

/* Nur für Style Queries (keine Größe) */
.container {
  container-type: normal;
}

Empfehlung: inline-size ist der häufigste Fall. size nur nutzen, wenn du wirklich auf die Höhe reagieren musst – es erfordert explizite Höhenangaben.

Container benennen

/* Kurzform */
.sidebar {
  container: sidebar / inline-size;
}

/* Langform */
.sidebar {
  container-name: sidebar;
  container-type: inline-size;
}

/* Mehrere Namen möglich */
.widget {
  container-name: widget sidebar-item;
  container-type: inline-size;
}

Container Query Syntax

Größen-Queries

/* Mindestbreite */
@container (min-width: 400px) { }

/* Maximalbreite */
@container (max-width: 599px) { }

/* Bereich */
@container (400px <= width <= 800px) { }

/* Benannter Container */
@container sidebar (min-width: 300px) { }

/* Höhe (nur mit container-type: size) */
@container (min-height: 200px) { }

/* Kombinationen */
@container (min-width: 400px) and (max-width: 800px) { }
@container card (width > 500px) or (height > 400px) { }

Container Query Units

Neue relative Einheiten, die sich auf den Container beziehen:

.card-title {
  /* 5% der Container-Breite */
  font-size: 5cqi;

  /* 10% der Container-Höhe */
  padding-block: 10cqb;
}
┌─────────────────────────────────────────────────────────────┐
│ Container Query Units                                        │
├─────────────┬───────────────────────────────────────────────┤
│ cqw         │ 1% der Container-Breite                       │
│ cqh         │ 1% der Container-Höhe                         │
│ cqi         │ 1% der Container Inline-Size (meist Breite)   │
│ cqb         │ 1% der Container Block-Size (meist Höhe)      │
│ cqmin       │ Kleinerer Wert von cqi/cqb                    │
│ cqmax       │ Größerer Wert von cqi/cqb                     │
└─────────────┴───────────────────────────────────────────────┘

Praktisches Beispiel:

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

.card-title {
  /* Skaliert mit Container-Breite, aber mit Grenzen */
  font-size: clamp(1rem, 5cqi, 2rem);
}

.card-image {
  /* Immer 40% der Container-Breite */
  width: 40cqi;
}

Praktische Beispiele

Responsive Card-Komponente

.card-wrapper {
  container: card / inline-size;
}

.card {
  display: grid;
  gap: 1rem;
  padding: 1rem;
  background: var(--surface);
  border-radius: 0.5rem;
}

.card-image {
  aspect-ratio: 16/9;
  object-fit: cover;
  border-radius: 0.25rem;
}

/* Klein: Vertikal gestapelt */
@container card (max-width: 399px) {
  .card {
    grid-template-columns: 1fr;
  }

  .card-title {
    font-size: 1.25rem;
  }
}

/* Mittel: Bild links, Content rechts */
@container card (400px <= width <= 699px) {
  .card {
    grid-template-columns: 150px 1fr;
    align-items: start;
  }

  .card-image {
    aspect-ratio: 1;
  }

  .card-title {
    font-size: 1.5rem;
  }
}

/* Groß: Mehr Platz für Bild */
@container card (min-width: 700px) {
  .card {
    grid-template-columns: 300px 1fr;
  }

  .card-title {
    font-size: 2rem;
  }

  .card-description {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
}
.nav-container {
  container: nav / inline-size;
}

.nav {
  display: flex;
  gap: 0.5rem;
}

.nav-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
}

.nav-label {
  display: none;
}

/* Kompakt: Nur Icons */
@container nav (max-width: 299px) {
  .nav {
    justify-content: center;
  }

  .nav-icon {
    font-size: 1.5rem;
  }
}

/* Normal: Icons + Labels */
@container nav (min-width: 300px) {
  .nav-label {
    display: inline;
  }
}

/* Breit: Zusätzliche Infos */
@container nav (min-width: 500px) {
  .nav-link {
    padding: 0.75rem 1rem;
  }

  .nav-badge {
    display: inline-flex;
  }
}

Dashboard-Widget

.widget {
  container: widget / inline-size;
  padding: 1rem;
  background: var(--surface);
  border-radius: 0.5rem;
}

.widget-chart {
  height: 200px;
}

.widget-stats {
  display: grid;
  gap: 0.5rem;
}

/* Kompakt */
@container widget (max-width: 249px) {
  .widget-header {
    flex-direction: column;
    text-align: center;
  }

  .widget-stats {
    grid-template-columns: 1fr;
  }

  .widget-chart {
    height: 150px;
  }
}

/* Normal */
@container widget (250px <= width <= 399px) {
  .widget-stats {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Breit */
@container widget (min-width: 400px) {
  .widget-stats {
    grid-template-columns: repeat(4, 1fr);
  }

  .widget-chart {
    height: 300px;
  }
}

Style Queries (Experimentell)

Style Queries ermöglichen Abfragen auf CSS Custom Properties:

.card-wrapper {
  container-type: normal; /* Keine Größe nötig */
  --theme: light;
}

.card-wrapper.dark {
  --theme: dark;
}

/* Reagiert auf Custom Property */
@container style(--theme: dark) {
  .card {
    background: #1a1a1a;
    color: #fff;
  }

  .card-title {
    color: #60a5fa;
  }
}

@container style(--theme: light) {
  .card {
    background: #fff;
    color: #1a1a1a;
  }
}

Hinweis: Style Queries haben noch eingeschränkte Browser-Unterstützung (Stand 2025).

Browser-Unterstützung

┌─────────────────────────────────────────────────────────────┐
│ Container Queries Browser Support (Januar 2025)              │
├──────────────────┬──────────────────────────────────────────┤
│ Chrome           │ ✅ 105+ (August 2022)                    │
│ Edge             │ ✅ 105+ (August 2022)                    │
│ Safari           │ ✅ 16+ (September 2022)                  │
│ Firefox          │ ✅ 110+ (Februar 2023)                   │
├──────────────────┼──────────────────────────────────────────┤
│ Global Support   │ ~93% (caniuse.com)                       │
├──────────────────┴──────────────────────────────────────────┤
│                                                             │
│ Style Queries: Nur Chrome 111+ (experimentell)              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Fallback-Strategie

.card {
  /* Fallback: Immer vertikal */
  display: flex;
  flex-direction: column;
}

/* Progressive Enhancement */
@supports (container-type: inline-size) {
  .card-wrapper {
    container-type: inline-size;
  }

  @container (min-width: 400px) {
    .card {
      flex-direction: row;
    }
  }
}

Best Practices

1. Container sinnvoll wählen

/* GUT: Wrapper-Element als Container */
.card-wrapper {
  container-type: inline-size;
}

/* SCHLECHT: Das Element selbst als Container */
.card {
  container-type: inline-size; /* Kann nicht auf eigene Größe reagieren! */
}

Regel: Ein Element kann nicht auf seine eigene Container-Größe reagieren – nur auf die eines Vorfahren.

2. Performance beachten

/* Vermeide container-type: size wenn möglich */
.widget {
  container-type: size; /* Braucht explizite Höhe! */
  height: 400px;        /* Muss gesetzt werden */
}

/* Besser: inline-size reicht meist */
.widget {
  container-type: inline-size;
}

3. Mit Media Queries kombinieren

/* Media Query für Layout-Grundstruktur */
@media (min-width: 768px) {
  .layout {
    display: grid;
    grid-template-columns: 1fr 300px;
  }
}

/* Container Query für Komponenten */
@container (min-width: 400px) {
  .card {
    flex-direction: row;
  }
}

4. Benennung für Klarheit

/* Ohne Namen: Nächster Container-Vorfahre */
@container (min-width: 400px) { }

/* Mit Namen: Explizit */
@container sidebar (min-width: 300px) { }
@container main-content (min-width: 600px) { }

Container Queries vs. Media Queries

┌────────────────────────────────────────────────────────────┐
│                    Wann was nutzen?                         │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  Media Queries:                                            │
│  • Seiten-Layout (Grid, Spalten)                           │
│  • Navigation (Burger-Menü vs. horizontal)                 │
│  • Schriftgrößen für die ganze Seite                       │
│  • Print-Styles                                            │
│  • Dark Mode (prefers-color-scheme)                        │
│                                                            │
│  Container Queries:                                        │
│  • Wiederverwendbare Komponenten (Cards, Widgets)          │
│  • Komponenten in unterschiedlichen Kontexten              │
│  • Design-System-Komponenten                               │
│  • Sidebar-Inhalte                                         │
│  • Dashboard-Widgets                                       │
│                                                            │
└────────────────────────────────────────────────────────────┘

Fazit

Container Queries sind ein Game-Changer für komponentenbasiertes CSS:

  • Intrinsisch responsive: Komponenten passen sich ihrem Kontext an
  • Wiederverwendbar: Gleiche Komponente funktioniert überall
  • Wartbar: Keine speziellen Klassen für “sidebar-card” vs “main-card”
  • Zukunftssicher: Teil des CSS-Standards mit exzellenter Browser-Unterstützung

Die Kombination aus Media Queries für das Seiten-Layout und Container Queries für Komponenten ist der moderne Ansatz für responsive Webdesign.