CSS container queries: components that respond to their own space

By

Published Updated 2 min read

Media queries ask about the viewport. But a card in a sidebar and the same card in a wide grid need different layouts at the same viewport width. Container queries let a component respond to the size of its container instead.

Size queries

Declare a container, then query it.

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


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


@container card (min-width: 480px) {
  .card {
    grid-template-columns: 160px 1fr;
  }
}

The same .card markup now stacks in a narrow sidebar and goes side by side in a wide column, wherever you place it. inline-size is almost always what you want; it only tracks the container's width.

Container query units

Units like cqi (1% of the container's inline size) let you scale things relative to the container.

CSS
.card h2 {
  font-size: clamp(1rem, 4cqi, 1.75rem);
}

Style queries

You can also query a container's custom properties. This is useful for variants and theming.

CSS
.panel {
  --tone: neutral;
}
.panel.danger {
  --tone: danger;
}


@container style(--tone: danger) {
  .panel-title {
    color: #b91c1c;
  }
}

Support for style queries on custom properties is not as universal as for size queries. Check caniuse before relying on it and provide a sensible default.

Scroll-state queries

Chromium (133+) supports querying scroll state, for example styling a sticky header only when it is stuck:

CSS
.header-wrap {
  container-type: scroll-state;
  position: sticky;
  top: 0;
}


@container scroll-state(stuck: top) {
  .header {
    box-shadow: 0 2px 8px rgb(0 0 0 / 0.15);
  }
}

Firefox and Safari do not support this yet, so treat it as progressive enhancement: the page must look fine without the shadow.

Using it in React and Tailwind

Container queries are plain CSS, so they work in any component. Tailwind CSS 4 supports them out of the box:

TypeScript / React
export function ProductCard({ product }: { product: Product }) {
  return (
    <div className="@container">
      <article className="grid gap-4 @md:grid-cols-[160px_1fr]">
        <img src={product.image} alt="" />
        <div>
          <h2 className="text-lg @lg:text-2xl">{product.name}</h2>
        </div>
      </article>
    </div>
  );
}

Practical advice

  • Use media queries for page-level layout and container queries for reusable components.
  • A container cannot query itself, so the styled element must be a descendant of the container.
  • Name containers when you nest them, so queries target the one you mean.

Sources and further reading