Starter Core concepts Container Queries

Core concept 04

Container Queries

Respond to the container, not the viewport. The same component adapts to wherever it's placed — sidebar, grid, or full-width — with no changes to the markup.

The problem with media queries

Media queries ask "how wide is the viewport?" This works for page-level layout. But for individual components, it's the wrong question. A card component placed in a narrow sidebar needs different styles than the same card in a wide content area — and both can exist simultaneously on the same page.

✗ Media query — viewport-aware only
/* Asks: "how wide is the screen?" */ @media (min-width: 640px) { .card { grid-template-columns: 180px 1fr; } } /* Card in sidebar? Also gets wide layout */ /* even when its container is 200px wide. */
✓ Container query — slot-aware
/* Asks: "how wide is my container?" */ @container card-slot (min-width: 380px) { .card { grid-template-columns: 180px 1fr; } } /* Card in sidebar stays stacked. */ /* Card in wide grid gets two columns.*/ /* Same markup, same CSS, same time. */

How it works

You opt a parent element into being a query container using container-type. Child elements can then use @container to query that container's dimensions and apply styles accordingly.

The two required steps
/* Step 1 — declare the container */
.card-wrapper {
  container-type: inline-size; /* query the inline (horizontal) axis */
  container-name: card-slot;   /* optional name for specificity */
}

/* Step 2 — query the container from inside it */
@container card-slot (min-width: 380px) {
  .card {
    grid-template-columns: 180px 1fr;
  }
}

container-type values

ValueQueries availableUse for
inline-sizeWidth (inline axis)Most responsive components
sizeWidth and heightComponents that need both axes
normalStyle queries onlyDefault — not a size container

Try it

The card below is inside a named container. Drag the slider to change the container width. Watch the layout adapt through three states — no viewport resize required.

Interactive demo — drag the slider

Container query

Responding to the container

Below 380px: stacked, no image.
380–560px: image + text, two columns.
Above 560px: image + text + aside, three columns.

Related

This column only appears when the container is ≥560px wide — from a single @container rule.

Real-world usage

The value of container queries is reusability. Write a component once; it adapts wherever it's placed.

A reusable card component
/* The container — anything that holds a card */
.card-grid,
.sidebar,
.featured-slot {
  container-type: inline-size;
  container-name: card-slot;
}

/* The card — written once, adapts to all three contexts */
.card {
  display: grid;
  grid-template-columns: 1fr;
}

@container card-slot (min-width: 380px) {
  .card { grid-template-columns: 160px 1fr; }
  .card__image { display: block; }
}

@container card-slot (min-width: 560px) {
  .card { grid-template-columns: 180px 1fr 180px; }
  .card__aside { display: block; }
}

Container queries work with tokens

Container queries and design tokens compose naturally. A card's layout responds to its container via @container, while its colours respond to the theme via semantic tokens. They're solving different problems and don't interfere with each other.

Container queries in the layer stack

Container query rules go in @layer components, alongside the base component styles they modify. Keeping them together makes the component's full responsive behaviour readable in one place.

components.css — correct placement
@layer components {

  /* Container declaration */
  .card-wrapper {
    container-type: inline-size;
    container-name: card-slot;
  }

  /* Base (smallest) state */
  .card {
    background: var(--surface-raised);
    display: grid;
    grid-template-columns: 1fr;
  }

  /* @container rules go inside the same @layer block */
  @container card-slot (min-width: 380px) {
    .card { grid-template-columns: 160px 1fr; }
  }

}

Browser support

Container queries landed in all major browsers by early 2023 and are safe to use without fallbacks for modern browser targets.

Chrome 105+ (Aug 2022)
Safari 16+ (Sep 2022)
Firefox 110+ (Feb 2023)