Container Queries: Resizing Components, Not Just Pages
Container queries are one of the most significant additions to modern CSS. They shift responsive design from being entirely viewport-dependent to being context-aware. The result is that components can now adapt to the space they are actually given, rather than making educated guesses based on the overall browser window. That unlocks layout patterns that were previously painfully fragile with media queries.
The case for component-level layouts
Consider a common design element: a two-column card with metadata on the left and content on the right. With media queries, collapsing this into a single column on mobile is trivial. The issue emerges when the internal component needs to adapt based on its own size, not the viewport’s size.
For example, the metadata column on this site has two layout modes: "sparse," where the key/value pairs sit in a single row, and "dense," where they stack vertically. You might assume you can coordinate media queries to handle both the top-level column collapse and the internal metadata shift. But there is no guaranteed linear relationship between the viewport’s width and the width of a specific container as the layout reflows.
One quick-and-dirty method is to tie the internal layout directly to the viewport using multiple media queries:
.metadata-column {
/* Condensed styles here */
@media (min-width: 35rem) and (max-width: 42rem),
(min-width: 60rem) {
/* Sparse styles here */
}
}
This relies on a comma-separated list of media conditions, which works like an OR operator. The "sparse" styles apply within specific viewport ranges that appear to work with the current layout. The problem is that those breakpoints are not part of any design system—they are magic numbers derived by manually resizing the window. Any future change, like adjusting column padding or the gap between elements, can silently break this fragile setup, causing overflow that only shows up in a narrow band of screen sizes.
Switching to container queries eliminates the guesswork. Instead of trying to anticipate how the viewport relates to a specific column, you measure the column itself:
<style>
.metadata-column {
container-type: inline-size;
}
.metadata {
/* Condensed styles here */
@container (min-width: 19rem) {
/* Sparse styles here */
}
}
</style>
<div class="metadata-column">
<div class="metadata">
<!-- Stuff here -->
</div>
</div>
The @container at-rule behaves like @media, but it checks the size of a designated container element. You wire up that element with the container-type property. In this case, the styling decision is based on one intentional threshold—the point where the sparse layout gets too cramped—rather than three arbitrary viewport widths. The component now adapts correctly whether it sits in a 40rem sidebar, a full-width main content area, or anywhere in between.
Targeting specific containers
The basic container-type property works with the nearest ancestor that establishes a container. You are not stuck with that default behavior, though. If you have nested containers, you can choose which one to watch.
container-name gives a container a label. You can then reference that label in a @container rule, forcing it to react to a specific ancestor regardless of the element’s immediate parent.
The container shorthand property simplifies this, letting you define both in one declaration:
main {
container: outer / inline-size;
/* Equivalent to: */
container-name: outer;
container-type: inline-size;
}
The slash is a modern CSS convention used to separate groups of values, not division. In practice, we worked with a nested .child element inside a <section> inside a <main>. By default, querying the child measures the <section>. Adding a container-name to the <main> and targeting that in the at-rule allows you to scale or layout the child based on the grandparent’s width instead.
Designing for the space you have
This context-based approach is not just a nicety for developer experience; it enables a whole class of designs known as "intrinsic" or "container-driven" layouts. It makes a component composable: you can place the same newslette r, product card, or sidebar widget in a narrow panel or a sprawling main column, and it will rearrange its internal structure to fit both without ever checking the browser window’s dimensions.
The most useful pattern is creating independent responses within broader media query layouts. When the main layout collapses from two columns to one, the content doesn’t just get pushed up or down—it dynamically reflows perfectly because it is checking its own available space. This gets around the pitfalls of sizing complex content and paves the way for layered layouts that respond more naturally across different breakpoints.



