A media query knows the viewport. A container query knows the space a component actually has. That difference matters when the same card appears in a full-width grid, a narrow sidebar, and a split layout.

Viewport breakpoints force the card to guess where it lives. Container queries let the card respond to its nearest query container instead. The layout owns available space. The component owns how it uses that space.

Use container queries for component decisions

Viewport decision

@media (width > 60rem) {
  .card {
    grid-template-columns: 10rem 1fr;
  }
}

Component decision

@container (width > 32rem) {
  .card {
    grid-template-columns: 10rem 1fr;
  }
}

The second rule does not care whether the viewport is 800 or 1800 pixels wide. It runs when the card's container crosses 32rem. That makes it useful for components that move between page regions or appear inside user-defined dashboards.

The smallest useful setup

Size queries need a query container. In most horizontal interfaces, inline-size is the right type because the component changes along its inline axis.

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

@container (width > 32rem) {
  .card {
    display: grid;
    grid-template-columns: 10rem 1fr;
  }
}

The @container rule selects descendants of the matching container. It does not style the query container itself. If one element needs to be both the measurable wrapper and the visual component, add a wrapper or query an outer component shell.

Keep the base layout useful. The narrow version should work before the query runs. The container query then enhances it when more space becomes available.

A card that works in a sidebar and a grid

<div class="card-slot">
  <article class="card">
    <img src="cover.webp" alt="">
    <div>
      <h2>Container queries</h2>
      <p>The same component, without page-specific modifiers.</p>
    </div>
  </article>
</div>
.card {
  display: grid;
  gap: 1rem;
}

.card img {
  aspect-ratio: 16 / 9;
  inline-size: 100%;
  object-fit: cover;
}

@container (width >= 32rem) {
  .card {
    grid-template-columns: minmax(8rem, 0.7fr) 1.3fr;
    align-items: center;
  }

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

The component changes because its slot is wide enough, not because a page template added .card--horizontal. The image ratio changes at the same boundary, so the visual and structural decisions stay together.

Name containers when the nearest one is ambiguous

An unnamed query uses the nearest ancestor with the required container type. That is convenient until another container is introduced between the component and the wrapper you meant to measure. A name makes the dependency explicit.

.results-panel {
  container: results / inline-size;
}

@container results (width >= 48rem) {
  .result-card {
    grid-template-columns: 12rem 1fr auto;
  }
}

Do not name every container by default. Use a name when the component must respond to one specific layout boundary or when nested query containers make the source unclear.

Scale details with container query units

Container query units are relative to the selected query container. 1cqi equals one percent of its inline size. They are useful for fluid details between discrete layout changes.

.card h2 {
  font-size: clamp(1.25rem, 1rem + 2cqi, 2rem);
}

.card {
  padding: clamp(1rem, 4cqi, 2rem);
}

Keep the clamp(). Unbounded container units can produce tiny text in a narrow slot and oversized text in a wide one. The minimum and maximum preserve the component's usable range.

Three mistakes that make container queries harder

  • Querying every wrapper. Put containment at meaningful component boundaries. A page full of anonymous containers is difficult to debug.
  • Recreating viewport breakpoints. Choose thresholds from the component's content, not from the device widths already in your design system.
  • Removing media queries entirely. Viewport queries still fit page-level decisions, user preferences, input capabilities, and global navigation changes.

Container style queries also exist, but support and syntax vary by the style being queried. Custom property style queries are the established starting point. Treat newer scroll-state and anchored query features separately from the widely supported size-query core.

A fallback can be the default layout

Container size queries are widely available in modern browsers. If your support policy includes older versions, keep the stacked component as the baseline and add the wider arrangement conditionally.

.card {
  display: grid;
  gap: 1rem;
}

@supports (container-type: inline-size) {
  .card-slot {
    container-type: inline-size;
  }

  @container (width >= 32rem) {
    .card {
      grid-template-columns: 10rem 1fr;
    }
  }
}

The fallback is not a second responsive system. It is simply a usable component. Add a media-query fallback only when the horizontal layout is essential, not when it is a refinement.

Primary references