Zum Inhalt springen

Portable UI-Module mit CSS Container Queries

Wie CSS Container Queries Komponenten ermöglichen, die sich an die Größe des Elternelements statt an den Viewport anpassen — portabel in jedem Layout.

5 Min. Lesezeit
Vergleich von Media Queries, die auf die Viewport-Breite abzielen, mit Container Queries, die auf die Breite des Elternelements abzielen, für responsives Komponentendesign

Media Queries haben uns responsives Design beigebracht, aber sie beantworten die falsche Frage. „Wie breit ist der Viewport?" hilft einer Card-Komponente nicht weiter, die in einer Sidebar, einem Hauptinhaltsbereich und einem modalen Dialog gerendert wird – alles bei derselben Viewport-Breite. Container Queries lösen das, indem sie fragen: „Wie breit ist mein Elternelement?"

Dieser Wechsel vom globalen Viewport-Bewusstsein zum lokalen Container-Bewusstsein verändert, wie wir Komponentenbibliotheken bauen. Komponenten werden zu in sich geschlossenen responsiven Modulen, die sich anpassen, wo immer sie platziert werden.

Die Grundlagen der Container Queries

Container Queries benötigen zwei Dinge: einen Containment-Kontext auf einem Elternelement zu deklarieren und größenbasierte Abfragen gegen diesen Container zu schreiben.

csscss
/* ❌ Media queries — component doesn't know its context */
.card {
  display: grid;
  grid-template-columns: 1fr;
}
 
@media (min-width: 768px) {
  .card {
    grid-template-columns: 200px 1fr;
  }
}
/* This breaks when .card is in a narrow sidebar at 1024px */
csscss
/* ✅ Container queries — component responds to parent size */
.card-wrapper {
  container-type: inline-size;
  container-name: card;
}
 
.card {
  display: grid;
  grid-template-columns: 1fr;
}
 
@container card (min-width: 400px) {
  .card {
    grid-template-columns: 200px 1fr;
  }
}
 
@container card (min-width: 600px) {
  .card {
    grid-template-columns: 250px 1fr auto;
  }
}

Das container-type: inline-size etabliert Size-Containment auf der Inline-Achse. Man kann auch size für beide Achsen verwenden, aber inline-size ist die übliche Wahl, da die meisten Layouts nur breitenbasierte Responsivität benötigen.

Container-Query-Einheiten

Container Queries führen neue Einheiten ein, die sich auf die Dimensionen des Containers beziehen, ähnlich wie sich Viewport-Einheiten auf den Viewport beziehen.

csscss
.card-wrapper {
  container-type: inline-size;
}
 
.card-title {
  /* 5% of the container's inline size */
  font-size: clamp(1rem, 5cqi, 2rem);
}
 
.card-image {
  /* Fixed height that scales with container */
  height: 30cqb;
}
 
.card-body {
  /* Padding relative to container width */
  padding: 2cqi;
}

Die Einheiten sind cqw (Container Query Width), cqh (Höhe), cqi (Inline-Größe), cqb (Block-Größe), cqmin (kleinere Dimension) und cqmax (größere Dimension). Sie folgen demselben Logical-Property-Modell wie margin-inline oder padding-block.

Eine responsive Produktkarte bauen

Eine echte Produktkarte muss mindestens drei Layout-Kontexte beherrschen: schmal (Sidebar, mobil), mittel (Grid-Zelle) und breit (hervorgehobener Slot). Container Queries bewältigen alle drei, ohne zu wissen, wo die Karte erscheint.

csscss
/* Container setup */
.product-card-container {
  container-type: inline-size;
  container-name: product;
}
 
/* Base: stacked layout for narrow containers */
.product-card {
  display: grid;
  grid-template-areas:
    "image"
    "content"
    "actions";
  gap: 1rem;
  padding: 1rem;
  border-radius: 8px;
  background: var(--surface-1);
}
 
.product-card__image {
  grid-area: image;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 4px;
  width: 100%;
}
 
.product-card__content {
  grid-area: content;
}
 
.product-card__actions {
  grid-area: actions;
  display: flex;
  gap: 0.5rem;
}
 
/* Medium: side-by-side layout */
@container product (min-width: 480px) {
  .product-card {
    grid-template-areas:
      "image content"
      "image actions";
    grid-template-columns: 200px 1fr;
    grid-template-rows: 1fr auto;
  }
 
  .product-card__image {
    aspect-ratio: 1;
    height: 100%;
  }
 
  .product-card__actions {
    justify-content: flex-end;
  }
}
 
/* Wide: featured layout with more detail */
@container product (min-width: 720px) {
  .product-card {
    grid-template-areas:
      "image content actions";
    grid-template-columns: 280px 1fr auto;
    align-items: center;
  }
 
  .product-card__image {
    aspect-ratio: 4 / 3;
  }
 
  .product-card__actions {
    flex-direction: column;
    align-self: center;
  }
}

Diese einzelne Komponentendefinition funktioniert in einem zweispaltigen Grid, einem Sidebar-Widget, einem Modal oder einer hervorgehobenen Sektion über die volle Breite. Kein JavaScript. Kein prop-basiertes responsives Umschalten. Das CSS regelt es.

Verschachtelte Container und Style Queries

Container können verschachtelt werden, und jede @container-Regel zielt auf den nächstgelegenen passenden Container, sofern kein Name angegeben ist. Style Queries erweitern dieses Muster, indem sie berechnete Style-Werte auf einem Container abfragen.

csscss
/* Nested container setup */
.dashboard-panel {
  container-type: inline-size;
  container-name: panel;
}
 
.widget-slot {
  container-type: inline-size;
  container-name: widget;
}
 
/* Target specific containers by name */
@container panel (min-width: 800px) {
  .widget-slot {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
  }
}
 
@container widget (min-width: 300px) {
  .metric-card {
    flex-direction: row;
    align-items: center;
  }
 
  .metric-card__value {
    font-size: 2rem;
  }
}
 
/* Style queries — respond to custom property values */
@container style(--theme: compact) {
  .metric-card {
    padding: 0.5rem;
    gap: 0.25rem;
  }
 
  .metric-card__label {
    font-size: 0.75rem;
  }
}
 
@container style(--theme: spacious) {
  .metric-card {
    padding: 1.5rem;
    gap: 1rem;
  }
 
  .metric-card__label {
    font-size: 1rem;
  }
}

Style Queries gewinnen noch an Browser-Unterstützung, aber sie erschließen ein mächtiges Muster: Elternkomponenten können Custom Properties setzen, auf die Kindkomponenten reagieren – ohne JavaScript. Eine Panel-Komponente setzt --theme: compact, wenn sie in einem dichten Layout steckt, und alle Kinder passen sich automatisch an.

Integration mit Komponenten-Frameworks

Moderne Komponentenbibliotheken profitieren von Container Queries als Styling-Primitive. So integriert man sie sauber mit React-Komponenten.

tsxtsx
// ❌ JavaScript-based responsive switching
function ProductCard({ layout }: { layout: "narrow" | "wide" }) {
  return (
    <div className={`card card--${layout}`}>
      {/* Parent must know and pass layout variants */}
    </div>
  );
}
tsxtsx
// ✅ Container query-based — component adapts automatically
function ProductCard({
  product,
}: {
  product: { name: string; price: number; image: string };
}) {
  return (
    <div className="product-card-container">
      <article className="product-card">
        <img
          className="product-card__image"
          src={product.image}
          alt={product.name}
          loading="lazy"
        />
        <div className="product-card__content">
          <h3>{product.name}</h3>
          <p className="product-card__price">
            ${product.price.toFixed(2)}
          </p>
        </div>
        <div className="product-card__actions">
          <button type="button">Add to Cart</button>
        </div>
      </article>
    </div>
  );
}
 
// Works everywhere — no layout prop needed
function App() {
  return (
    <>
      <aside className="sidebar">
        <ProductCard product={featured} />
      </aside>
 
      <main className="content-grid">
        {products.map((p) => (
          <ProductCard key={p.id} product={p} />
        ))}
      </main>
    </>
  );
}

Die entscheidende Erkenntnis ist, dass die Container-Deklaration auf dem eigenen Wrapper-Element der Komponente lebt. Die Komponente erstellt ihren eigenen Containment-Kontext und wird dadurch vollständig portierbar.

Praktische Migrationsstrategie

Man muss nicht alle Media Queries auf einmal umschreiben. Container Queries und Media Queries koexistieren. Der praktische Migrationspfad beginnt auf Komponentenebene.

csscss
/* Step 1: Add container context to layout regions */
.main-content { container-type: inline-size; }
.sidebar { container-type: inline-size; }
.modal-body { container-type: inline-size; }
 
/* Step 2: Convert component media queries to container queries */
/* Keep page-level layout media queries as-is */
@media (min-width: 768px) {
  .page-layout {
    grid-template-columns: 300px 1fr;
  }
}
 
/* Convert component-level queries to container queries */
@container (min-width: 400px) {
  .card { grid-template-columns: 150px 1fr; }
}
 
/* Step 3: Use feature detection for progressive enhancement */
@supports (container-type: inline-size) {
  .card-wrapper {
    container-type: inline-size;
  }
 
  @container (min-width: 400px) {
    .card {
      grid-template-columns: 150px 1fr;
    }
  }
}
 
@supports not (container-type: inline-size) {
  @media (min-width: 768px) {
    .card {
      grid-template-columns: 150px 1fr;
    }
  }
}

Die wichtigsten Erkenntnisse

Container Queries verlagern responsives Design vom Viewport-zentrierten zum komponentenzentrierten Ansatz und lassen Komponenten sich anhand des tatsächlich eingenommenen Platzes statt der Browserfensterbreite anpassen. Die Deklaration container-type: inline-size etabliert das Containment, während @container-Regeln den nächstgelegenen benannten oder unbenannten Container-Vorfahren abfragen. Container-Query-Einheiten (cqi, cqb, cqw, cqh) lassen Schriftgrößen, Padding und Dimensionen relativ zum Container statt zum Viewport skalieren. Das Verschachteln von Containern mit expliziten Namen gibt präzise Kontrolle darüber, auf welchen Vorfahren eine Abfrage zielt. Style Queries erweitern dieses Modell, indem Kinder auf Custom-Property-Werte reagieren können, die von ihrem Container gesetzt werden – und ermöglichen so theme-ähnliches Umschalten ohne JavaScript. Der Migrationspfad ist inkrementell: Media Queries für Layout-Entscheidungen auf Seitenebene beibehalten und Container Queries für Responsivität auf Komponentenebene übernehmen, mit @supports für Progressive Enhancement in Umgebungen mit gemischter Unterstützung.

Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX