Frontend / CSS / 02_container_queries_and_subgrid.md

Container Queries and Subgrid

Updated 4 interview angles 4 min read source
On this page9
  1. What problem do container queries solve that media queries can’t?
  2. How do you set up a container, and what’s container-type?
  3. What are container query units?
  4. What is subgrid and what does it fix?
  5. Container queries vs media queries — do you replace one with the other?
  6. Gotchas
  7. Related
  8. Further reading
  9. Interview angle

Container Queries and Subgrid

Two modern layout features that fix long-standing gaps. Container queries (@container) style an element by the size of its container rather than the viewport — true component-level responsiveness, so the same card adapts whether it’s in a wide main area or a narrow sidebar. Subgrid lets a nested grid align to its parent’s tracks, so content in sibling cards lines up across cards. Both are baseline-available in current browsers.

What problem do container queries solve that media queries can’t?

Media queries respond to the viewport, but a reusable component doesn’t know how wide it will be rendered — it depends on where it’s placed. Container queries respond to an ancestor container’s size, so a component is responsive to its own context:

css
.card-wrap { container-type: inline-size; }   /* establish a query container */

@container (min-width: 400px) {
  .card { grid-template-columns: 120px 1fr; }  /* side-by-side when the container is wide */
}

Drop that card in a 320px sidebar → stacked; in an 800px main → side-by-side. No viewport breakpoints, no knowing the layout in advance.

How do you set up a container, and what’s container-type?

  • container-type: inline-size — query the width only (most common; height stays content-driven).
  • container-type: size — query width and height (requires the container to have a definite size, or it collapses).
  • container-type: normal — not a size container (still usable for style/container-name).
  • container-name: card + @container card (…) — name containers to target a specific ancestor.

Shorthand: container: card / inline-size;.

What are container query units?

Length units relative to the query container: cqw/cqh (1% of container width/height), cqi/cqb (inline/block size), cqmin/cqmax. Useful for fluid type/spacing scaled to the component:

css
.card h2 { font-size: clamp(1rem, 5cqi, 2rem); }   /* scales with the card, not the viewport */

What is subgrid and what does it fix?

A nested grid normally has its own independent tracks. grid-template-columns: subgrid (or -rows) makes the child grid adopt the parent’s track lines, so descendants align across separate grid items:

css
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
.card  { display: grid; grid-row: span 3; grid-template-rows: subgrid; }
/* each card's title / body / footer align on shared rows across all cards */

Without subgrid, each card’s internal rows size independently and titles/footers don’t line up when content length differs. Subgrid is the clean fix for “make these card sections align.”

Container queries vs media queries — do you replace one with the other?

They’re complementary. Media queries still suit page-level layout (overall columns, navigation mode) and non-size conditions (prefers-color-scheme, prefers-reduced-motion, print). Container queries suit component-level adaptation. Modern systems use media queries for the shell and container queries for reusable components.

Gotchas

  • You can’t query the element itself — @container queries the nearest ancestor container, so you need a wrapper element to be the container (a component can’t be its own size container for its root). Plan one wrapper per queryable component.
  • container-type: size needs a definite height or the container has zero block size and collapses; inline-size is the safe default.
  • container-type establishes containment (layout/style/inline-size) — it creates a new containing context, which can affect things like position: fixed descendants and % heights. Mostly fine with inline-size.
  • Subgrid only inherits tracks in the axis you set — subgrid on rows doesn’t affect columns; set both if needed. The gap can be inherited or overridden.
  • Naming collisions — unnamed @container matches the nearest container of any name; name containers when nesting to avoid querying the wrong ancestor.

Further reading

Interview angle 4

  • “What problem do container queries solve that media queries do not?” - a component that must adapt to the space it is given rather than to the viewport. The same card in a narrow sidebar and in a wide main column needs two layouts, and a media query cannot tell those apart because the viewport is identical.
    • Follow-up: “Why can’t an element query itself?” - because its own size would then depend on the rule that depends on its size. You declare container-type: inline-size on a wrapper and query that, which breaks the circularity.
  • “What do container query units buy you?” - type and spacing that scale with the component rather than the page. 1cqi is one percent of the container’s inline size, so a card’s heading stays proportionate wherever the card is placed, with no breakpoint at all.
  • “Two cards side by side have sections that do not line up. What fixes it?” - subgrid. Each card’s internal grid adopts the parent’s tracks instead of defining its own, so titles, bodies and footers align across siblings. Before subgrid this needed fixed heights or JavaScript measurement.
  • “So do container queries replace media queries?” - no, they split the job. Media queries own the page shell and everything that is not about size - prefers-color-scheme, prefers-reduced-motion, print. Container queries own reusable components, which is exactly where the viewport was the wrong thing to ask about.