Container queries
Container queries let a component respond to the width of its container instead of the viewport — so one card works in a sidebar and a wide column unchanged.
In one line: a container query styles an element by the size of its container rather than the size of the viewport — so the same card can sit in a narrow sidebar or a wide column and lay itself out correctly in both, with no page-level breakpoints.
What it is
A media query asks about the viewport: "how wide is the window?" It is a page-level fact, the same for every element on the screen. That works for whole-page layout, but it falls apart for components — a card doesn't care how wide the window is, it cares how much room it has been given.
A container query asks a different question: "how wide is my container?" You mark an ancestor as a query container, then write @container rules that respond to that box's size. The same component can now render stacked in a 280px sidebar and side-by-side in a 720px column, on the same page, at the same time — because each instance reads its own slot, not the shared viewport.
The shift is from page-level responsiveness to component-level responsiveness: the breakpoint moves out of the layout and into the component that owns it.
Why it matters
This is what makes a component truly reusable. With media queries, a card's "switch to a row" breakpoint is hard-coded to a viewport width — drop that card into a narrow sidebar on a wide screen and it tries to go side-by-side in a space that can't hold it. You end up forking the component, or threading layout props through every caller.
Container queries dissolve that coupling. Write the card once, mark its wrapper as a container, and let it respond to whatever space it lands in. The same markup works in a grid cell, a sidebar, a modal, or a full-width hero — the component carries its own responsive logic and stops caring where it is mounted.
See it
Tweak it3
Drag the width control and watch the card reflow on its own size — not the window's. Below the 380px mark it stacks the thumbnail over the text; at or above it, the card snaps into a side-by-side row. The viewport never changes; only the container does.
How it works
Three pieces wire up a container query. First, you nominate a query container with container-type: inline-size — this tells the browser to track that box's inline (horizontal) size and lets descendants query it. Optionally add container-name so a rule can target a specific named container instead of the nearest one.
Then you write the rule: @container card (min-width: 380px) { … } applies its styles only when the nearest container named card is at least 380px wide. It reads just like a media query, but the question is about the container, not the viewport.
Finally, container units let values scale fluidly with the container: cqi is 1% of the container's inline size, cqw/cqb map to its width/block size. font-size: clamp(1rem, 4cqi, 1.5rem) grows the heading with the container, not the page.
Build it
Mark the wrapper as a container, then switch the card from a stacked column to a side-by-side row at an inline-size breakpoint — and let the heading scale with cqi so it stays proportional in any slot.
/* 1. Nominate the wrapper as a query container. */
.card-wrap {
container-type: inline-size;
container-name: card;
}
/* 2. Default (narrow): stack the thumbnail over the text. */
.card {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.card__title {
/* Fluid with the container: 1% of inline size per cqi. */
font-size: clamp(1rem, 4cqi, 1.5rem);
}
/* 3. When the *container* is wide enough, lay out side-by-side. */
@container card (min-width: 380px) {
.card {
flex-direction: row;
align-items: center;
}
}Make it yours
Use the controls beside the demo above to change container width, reflow breakpoint, and two-column copy when wide — each change updates the example live.
Experiment in the playground- Park the width just below 380px and nudge it up one step — the card flips from stacked to side-by-side the moment the container crosses the breakpoint, with the window untouched.
- Imagine the same card in two slots at once: a 300px sidebar and a 700px column. With a container query, one component serves both; with a media query it could only read the shared viewport.
- Try moving the breakpoint in your head — the number lives with the component, so the card stays correct wherever you mount it.
Reproduce it with an LLM
Reproduce it with an LLM
You are a senior front-end engineer. Build a reusable product-card component that adapts to the width of its CONTAINER, not the viewport, using CSS container queries. Mark the wrapper as a query container (`container-type: inline-size`), and at a small inline size stack the thumbnail above the text; at a larger inline size lay them out side by side. Use `cqi`/`cqw` units for one fluid value (e.g. a heading size). The same component must work in a narrow sidebar and a wide main column with no JS and no viewport media queries. Include a visible focus-visible style on the card's link and a prefers-reduced-motion-safe transition. Return only the HTML and CSS.
Pitfalls & accessibility
- Setting
container-type: inline-sizeestablishes size containment on that element — if a descendant relied on percentage heights resolved against it, double-check the layout, and prefer querying the inline axis only unless you truly needsize.