Viewport breakpoints are global guesses. They answer “how wide is the browser?” when many component bugs are really asking “how much room did this component get?”
Container queries are useful when a component can appear in multiple contexts: a card in a three-column grid, the same card in a sidebar, and the same card in a detail page. A viewport media query cannot know those contexts. A container query can.
This does not make media queries obsolete. It gives you a better boundary for component decisions. Page structure can still respond to the viewport. Reusable components can respond to their containers.
Establish the query boundary
The parent that owns available space needs containment.
.card-region {
container-type: inline-size;
}
.product-card {
display: grid;
gap: 1rem;
}
@container (min-width: 32rem) {
.product-card {
grid-template-columns: 12rem minmax(0, 1fr);
align-items: start;
}
}
This reads as a component rule, not a page rule. When the card region is wide enough, the card gets a media object layout. When it is narrow, it stacks.
Choose the query boundary deliberately. Good boundaries are layout regions that already define available space: grid cells, sidebars, rails, cards, content modules, and reusable slots. Poor boundaries are random wrappers added only because a selector needed a parent.
Container type is a contract
container-type: inline-size says the container can be queried by its inline size. That is the common case for responsive components because most layout decisions depend on available width.
Named containers are useful when nested containers exist.
.component-shell {
container-type: inline-size;
container-name: component-shell;
}
@container component-shell (min-width: 40rem) {
.component-shell__body {
grid-template-columns: 1fr 16rem;
}
}
Without a name, an @container rule uses the nearest eligible ancestor. That is often correct. In complex UI, a name prevents a future wrapper from silently changing which container owns the decision.
Query the component, not every value
Container query units such as cqi can be useful for type, spacing, and media treatments inside reusable modules.
.feature-card {
container-type: inline-size;
}
.feature-card h2 {
font-size: clamp(1.25rem, 8cqi, 2.25rem);
}
Use query units where local fluidity improves the component. Body text, compact controls, icon buttons, and dense dashboards often need stable sizing. A value that changes continuously can create awkward line breaks or make repeated UI harder to scan.
Watch intrinsic sizing
Container queries do not remove the need for good sizing rules. A child with a long URL, a wide table, or a fixed-width image can still overflow.
.product-card {
min-inline-size: 0;
}
.product-card img {
inline-size: 100%;
max-inline-size: 100%;
block-size: auto;
}
If a component refuses to behave at a container breakpoint, inspect intrinsic sizing before adding more queries. The problem may be that the content cannot shrink, not that the breakpoint is wrong.
When media queries still win
Use viewport media queries for page-level decisions: navigation shell changes, global spacing shifts, print behavior, and anything tied to device or viewport conditions. Use container queries for component layout decisions.
@media (min-width: 64rem) {
.page-shell {
grid-template-columns: 18rem minmax(0, 1fr);
}
}
@container (min-width: 32rem) {
.summary-card {
grid-template-columns: auto minmax(0, 1fr);
}
}
The page shell responds to the viewport. The card responds to the slot it receives. Mixing both is normal. The important part is keeping each rule responsible for the right question.
The production rule
Before shipping a container-query component, place it in real containers: a narrow sidebar, a medium content column, a wide main region, and a slot with long or missing content. If every usage needs a special class, the query boundary may be too low-level. If the same component survives those contexts without page-specific modifiers, the container query is doing useful work.
Compact states should reorganize content, not hide meaning. If a component removes visible labels in a narrow container, make sure the remaining controls still have accessible names.
Keep breakpoints semantic
Container breakpoints should describe a behavior change, not a device label. Name the reason in code comments or component documentation when it is not obvious: the media object has enough room for a side image, the filter bar can keep actions inline, or the comparison card can show secondary metadata. That makes future edits safer than a magic 32rem value with no explanation.
Avoid scattering many tiny query adjustments through a component. If every child changes at a different width, the component may have too many responsibilities or the base layout may be too rigid. A useful container query usually changes one structural relationship, then lets normal flow handle the details.
Container queries are strongest when paired with content testing. Put the same component in the smallest slot where it is allowed to appear, add the longest expected label, and check zoomed text. If the compact version hides important context or relies on icons without names, the query solved a layout problem by creating a usability problem.
Related guides: Grid vs Flexbox, Intrinsic sizing, and Fluid spacing without breakpoint sprawl.