Responsive and modern CSS
Container queries
Adapt a reusable component to the space provided by its own container instead of coupling it to the full viewport width.
Media queries have a blind spot. The same card component can sit in a wide main column and in a narrow sidebar, on the same page, at the same viewport width. A media query can’t tell the two apart because it only knows about the viewport.
Container queries fix this. The component looks at the space its own container gives it, and adapts to that.
Set up a container
You need two things. Mark an ancestor as a container, then write a query against it:
.card-wrapper {
container-name: card;
container-type: inline-size;
}
@container card (min-width: 30rem) {
.card {
display: grid;
grid-template-columns: 10rem 1fr;
}
}
container-type: inline-size says “measure this element’s width and let descendants query it”. container-name gives it a name so the query can target it explicitly. Then @container card (min-width: 30rem) applies its rules whenever that wrapper is at least 30rem wide.
Now the card is a two-column grid in the main column and a stacked block in the sidebar. Same CSS, same viewport, different result.
Why the wrapper
Notice that the rules inside @container target .card, not .card-wrapper. A container query applies to the descendants of the container, never to the container itself. The element being measured can’t change its own layout based on the measurement, or you’d get a loop.
So the pattern is: an outer wrapper establishes the container, and the inner component reads it. If your component already has an outer element, use that. If not, add a wrapper.
Containment has a cost
Making an element a size container turns on size containment on the inline axis. The element’s width no longer depends on its children. That’s what prevents the loop, but a wrapper that used to shrink-wrap its content might now behave differently. After you add container-type, check the wrapper still has the width you expect.
Media queries and container queries together
They don’t compete. Use media queries for page-level decisions: how many columns the layout has, whether the sidebar exists at all. Use container queries for component-level decisions: how a card lays itself out in whatever space it lands in. A page usually needs both.
Container units
Along with the queries come new units. cqi is 1% of the query container’s inline size. It’s handy inside clamp() when a value should scale with the component rather than the viewport:
.card h2 {
font-size: clamp(1.25rem, 5cqi, 2rem);
}
The heading grows with its container and stops at 2rem so a very wide card doesn’t get a giant title.
Try it on the course page: put a copy of the same card in the sidebar and in the main column. Open the Container Queries panel in DevTools, which shows which ancestor and which threshold matched for each one. If the two cards render differently at one viewport width with no media query involved, you’ve got it.
Lesson completed