Container Queries for Component-Level Responsive CSS
Media queries answer a viewport-level question: how wide is the browser? Reusable components often need a different answer: how much space does this component have where it was placed?
CSS container queries solve that problem. A card can render compactly in a sidebar and switch to a horizontal layout in a wide content area without knowing either page layout in advance.
Why viewport breakpoints leak layout assumptions
Consider a reusable article card. A media query might switch it to two columns when the viewport exceeds 900 pixels:
@media (min-width: 900px) {
.card {
grid-template-columns: 12rem 1fr;
}
}That works until the card appears inside a narrow column on a large desktop. The viewport is wide, but the card is not. The component now applies a layout that does not fit its actual space.
Container queries move the breakpoint to a parent container.
Establish a query container
Set container-type: inline-size on the element whose available width should control descendants:
.card-list {
container-type: inline-size;
}Then query that container:
.card {
display: grid;
gap: 1rem;
}
@container (width >= 42rem) {
.card {
grid-template-columns: 12rem 1fr;
align-items: start;
}
}The card remains single-column until its nearest eligible query container reaches 42rem, regardless of viewport width.
Prefer logical dimensions
inline-size follows the document’s writing mode, which makes it a better default than size containment when you only need width-like behavior. It also avoids imposing unnecessary containment on the block dimension.
Name containers when nesting becomes ambiguous
Large interfaces often have nested containers. Naming the intended one makes the dependency explicit:
.dashboard-panel {
container: panel / inline-size;
}
@container panel (width >= 50rem) {
.summary {
grid-template-columns: repeat(3, 1fr);
}
}Without a name, the browser uses the nearest ancestor that can satisfy the query. That is convenient for small components but can become surprising after layout refactoring.
Use container query units for gradual scaling
Container queries are not limited to discrete breakpoints. Container query units such as cqi represent a percentage of the query container’s inline size.
For example:
.card-title {
font-size: clamp(1.1rem, 1rem + 1.5cqi, 1.6rem);
}This allows typography to grow with the component rather than the viewport. Keep sensible minimum and maximum values with clamp(); unconstrained fluid sizing can become unreadable in unusually small or large containers.
Combine grid flexibility with container queries
Do not replace every flexible layout with a breakpoint. CSS Grid and Flexbox can already absorb many size changes.
A robust pattern is:
.card-list {
container-type: inline-size;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1rem;
}Let Grid determine how many cards fit. Use a container query only when the internal design of each card needs to change.
This separation reduces breakpoint count and keeps CSS easier to reason about.
Understand what the query can style
A container query styles descendants of the queried container. It does not let an element query its own final size and then restyle itself in a circular way.
A common structure is therefore:
<div class="card-slot">
<article class="card">
...
</article>
</div>.card-slot {
container-type: inline-size;
}
@container (width >= 36rem) {
.card {
/* styles based on .card-slot width */
}
}If a component must be self-contained, its outer wrapper can serve as the query container while an inner element receives the adaptive styles.
Choose breakpoints from component pressure
Avoid translating existing device breakpoints mechanically. A component breakpoint should describe when the current layout stops working.
Resize the container and observe where labels wrap badly, controls collide, or whitespace becomes excessive. Set the breakpoint around that pressure point using relative units such as rem when appropriate.
The resulting value belongs to the component’s design, not to a list of phone, tablet, and desktop widths.
Progressive enhancement and fallbacks
Container queries are supported in current major browsers, but applications with older-browser requirements should still define a usable base layout.
Write the narrow or simplest component first, then enhance it inside @container. If container queries are unavailable, users still receive the base design.
When supporting a specific browser matrix, verify support against that project’s actual minimum versions rather than assuming every deployed client is current.
Common pitfalls
Adding containment to the wrong element
The query container must be an ancestor of the element being styled. Putting container-type on .card and trying to query .card itself will not produce the intended self-query behavior.
Querying everything
Container queries add another dependency to the cascade. Use intrinsic Grid/Flexbox behavior for continuous layout adaptation and reserve queries for meaningful design state changes.
Reusing viewport breakpoint names
Names such as tablet or desktop reintroduce page assumptions. Prefer semantic container names such as sidebar, panel, or article-list.
Forgetting nested-container behavior
An unnamed query can start matching a newly introduced nearer container after a refactor. Name important containers when the relationship should remain stable.
When container queries are a good fit
They are especially useful for cards, navigation modules, dashboards, data panels, reusable form groups, and design-system components that appear in several page regions.
Media queries still matter for viewport-level concerns such as application navigation, page gutters, and input capabilities. The two features complement each other: media queries describe the environment, while container queries describe the space a component actually receives.
Design components around their available space and they become easier to reuse without accumulating page-specific overrides.