Advanced10 min read

CSS Container Queries: Component-Based Responsive Design

10 min read
388 words
15 sections12 code blocks

CSS Container Queries represent a fundamental shift in responsive design thinking. Instead of components responding to the viewport width (media queries), they respond to their container's width. This makes components truly portable and reusable regardless of where they're placed in a layout.

The Viewport Problem

Media queries have served us well, but they have a fundamental limitation: they only know about the viewport, not the actual space available to a component.

CSS
/* Traditional approach - viewport-based */
.card {
  display: flex;
  flex-direction: column;
}

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

This works when the card is full-width, but what if the same card appears in a narrow sidebar? It would still use the horizontal layout because the viewport is wide—even though the card's container is narrow.

Enter Container Queries

Container queries solve this by letting components respond to their container's size.

CSS
/* Define a containment context */
.card-container {
  container-type: inline-size;
}

/* Component responds to container, not viewport */
.card {
  display: flex;
  flex-direction: column;
}

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

Now the card switches to horizontal layout when its container is at least 400px wide, regardless of viewport size.

Setting Up Containment

Before using container queries, you must establish a containment context using container-type.

CSS
/* Size containment on inline axis (width in horizontal writing) */
.container {
  container-type: inline-size;
}

/* Size containment on both axes */
.container {
  container-type: size;
}

/* Name the container for targeted queries */
.container {
  container-type: inline-size;
  container-name: card-wrapper;
}

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

Container Types

  • inline-size: Containment on the inline axis only (width in LTR). Most commonly used.
  • size: Containment on both axes. Requires explicit height on the container.
  • normal: No size containment (default). Container queries won't work.

Writing Container Queries

Container queries use @container instead of @media, with similar syntax.

CSS
/* Query any ancestor container */
@container (min-width: 300px) {
  .element { /* styles */ }
}

/* Query a named container */
@container card-wrapper (min-width: 300px) {
  .element { /* styles */ }
}

/* Multiple conditions */
@container (min-width: 300px) and (max-width: 600px) {
  .element { /* styles */ }
}

/* Width queries */
@container (width > 400px) {
  .element { /* styles */ }
}

@container (400px <= width <= 800px) {
  .element { /* styles */ }
}

Practical Example: Responsive Card

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

.card {
  display: grid;
  gap: 16px;
  padding: 16px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

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

.card-title {
  font-size: 1.125rem;
  font-weight: 600;
}

.card-description {
  color: #666;
  font-size: 0.875rem;
}

/* Small container: stack vertically */
@container (max-width: 299px) {
  .card {
    text-align: center;
  }

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

/* Medium container: side by side */
@container (min-width: 300px) {
  .card {
    grid-template-columns: 120px 1fr;
    grid-template-rows: auto 1fr;
  }

  .card-image {
    grid-row: span 2;
    aspect-ratio: 1;
  }
}

/* Large container: more prominent */
@container (min-width: 500px) {
  .card {
    grid-template-columns: 200px 1fr;
    padding: 24px;
    gap: 24px;
  }

  .card-image {
    aspect-ratio: 4/3;
  }

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

  .card-description {
    font-size: 1rem;
  }
}

Named Containers

Naming containers allows you to query specific ancestors, not just the nearest one.

CSS
.page-layout {
  container: layout / inline-size;
}

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

.main-content {
  container: main / inline-size;
}

/* Query specific containers */
@container layout (min-width: 1200px) {
  .navigation { /* full layout is wide */ }
}

@container sidebar (min-width: 250px) {
  .sidebar-widget { /* sidebar is wide enough */ }
}

@container main (min-width: 600px) {
  .article-card { /* main content area is wide */ }
}

Container Query Units

CSS introduces new units relative to container dimensions.

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

.card-title {
  /* 5% of container's inline size */
  font-size: clamp(1rem, 5cqi, 2rem);
}

.card-padding {
  /* 3% of container's inline size */
  padding: 3cqi;
}

Available Container Units

  • cqw: 1% of container's width
  • cqh: 1% of container's height
  • cqi: 1% of container's inline size
  • cqb: 1% of container's block size
  • cqmin: Smaller of cqi or cqb
  • cqmax: Larger of cqi or cqb
CSS
.responsive-text {
  /* Fluid typography based on container */
  font-size: clamp(0.875rem, 2cqi + 0.5rem, 1.5rem);

  /* Fluid spacing */
  padding: clamp(1rem, 4cqi, 3rem);

  /* Fluid border radius */
  border-radius: min(2cqi, 12px);
}

Combining with Media Queries

Container queries and media queries serve different purposes and work well together.

CSS
/* Layout-level decisions with media queries */
@media (min-width: 768px) {
  .page {
    display: grid;
    grid-template-columns: 300px 1fr;
  }
}

/* Component-level decisions with container queries */
.card-wrapper {
  container-type: inline-size;
}

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

Common Patterns

Responsive Navigation

CSS
.nav-container {
  container-type: inline-size;
}

.nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.nav-link {
  padding: 8px 16px;
}

/* Narrow: icon only */
@container (max-width: 400px) {
  .nav-link span {
    display: none;
  }
}

/* Wide: icons and text */
@container (min-width: 401px) {
  .nav-link {
    display: flex;
    align-items: center;
    gap: 8px;
  }
}

Responsive Data Display

CSS
.data-wrapper {
  container-type: inline-size;
}

/* Default: stacked cards */
.data-item {
  display: flex;
  flex-direction: column;
  padding: 16px;
  border-bottom: 1px solid #eee;
}

/* Medium: inline layout */
@container (min-width: 400px) {
  .data-item {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
  }
}

/* Wide: table-like */
@container (min-width: 600px) {
  .data-item {
    display: grid;
    grid-template-columns: 2fr 1fr 1fr 1fr;
    gap: 16px;
  }
}

Browser Support

Container queries have good modern browser support (Chrome 105+, Firefox 110+, Safari 16+). Use feature detection for older browsers.

CSS
/* Base styles work everywhere */
.card {
  display: flex;
  flex-direction: column;
}

/* Enhanced with container queries where supported */
@supports (container-type: inline-size) {
  .card-wrapper {
    container-type: inline-size;
  }

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

Summary

Container queries fundamentally change how we approach responsive design. Instead of components knowing about the global viewport, they respond to their immediate context. This creates truly reusable components that adapt appropriately whether placed in a full-width main area, a narrow sidebar, or a modal dialog. Combined with container query units, you can build entire component systems that scale fluidly based on available space.