Container Queries: Component-Level Responsiveness
For years, web developers have relied on media queries to create responsive designs. Media queries check the viewport size and adjust layouts accordingly. This approach works well for page-level design decisions, but it breaks down when you need individual components to respond to their own available space rather than the entire viewport.
Container queries represent a fundamental shift in how we approach responsive design. Instead of asking "how wide is the browser window?", we can now ask "how much space does this component have?" This changes everything about how we build reusable, responsive components.
The Problem with Media Queries
Consider a card component that needs to adapt its layout. Using media queries, you would write something like this:
.card {
display: flex;
flex-direction: column;
}
@media (min-width: 768px) {
.card {
flex-direction: row;
}
}
This works when the card spans the full viewport width. But what happens when you place the same card in a narrow sidebar? The viewport might be 1200px wide, triggering the horizontal layout, but the sidebar is only 300px wide. The card breaks because it is responding to the viewport, not its actual container.
This limitation has plagued component-based design for years. You could not create truly reusable components that adapt to their context. You had to create different versions of components or use complex JavaScript solutions to measure container sizes. Container queries solve this problem elegantly.
Understanding Container Queries
Container queries allow elements to respond to the size of their containing element rather than the viewport. To use container queries, you need two pieces: a container and a query that targets it.
Defining a Container
First, you designate an element as a container using the container-type property:
.sidebar {
container-type: inline-size;
}
The inline-size value means the container can be queried based on its inline dimension, which is the width in horizontal writing modes. This is the most common container type. Other values include size (both dimensions) and normal (the default, which means no containment).
When you set container-type: inline-size, you are creating what CSS calls a containment context. The browser tracks the size of this element, allowing descendant elements to query it. This containment is necessary for performance reasons. Without it, the browser would need to track every element, which would be expensive.
Querying the Container
Once you have defined a container, descendant elements can query it using the @container rule:
.card {
display: flex;
flex-direction: column;
}
@container (min-width: 500px) {
.card {
flex-direction: row;
}
}
This queries the nearest ancestor container. When that container is at least 500px wide, the card switches to a horizontal layout. The card no longer cares about the viewport size. It only cares about its container.
Named Containers and Scoping
By default, @container queries target the nearest ancestor with container-type set. But what if you have nested containers and need to query a specific one? This is where container names come in.
You can name a container using the container-name property:
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
.main-content {
container-type: inline-size;
container-name: main;
}
Then query specific containers by name:
@container sidebar (min-width: 300px) {
.card {
padding: var(--spacing-sm);
}
}
@container main (min-width: 800px) {
.card {
padding: var(--spacing-lg);
}
}
There is also a shorthand property that combines both container-type and container-name:
.sidebar {
container: sidebar / inline-size;
}
The syntax is container: name / type. This shorthand is convenient when you need to set both properties at once.
Container Query Units
Container queries introduce new length units that are relative to the container size, similar to how viewport units (vw, vh) relate to the viewport. These units make it easy to scale elements proportionally within their container.
- cqw: 1% of the container's width
- cqh: 1% of the container's height
- cqi: 1% of the container's inline size (width in horizontal writing modes)
- cqb: 1% of the container's block size (height in horizontal writing modes)
- cqmin: The smaller of cqi or cqb
- cqmax: The larger of cqi or cqb
These units are particularly useful for fluid typography and spacing that scales with the container:
.card {
container-type: inline-size;
}
.card h2 {
font-size: clamp(1.6rem, 5cqi, 3.2rem);
}
.card p {
font-size: clamp(1.4rem, 3cqi, 1.8rem);
}
In this example, the heading size scales between 1.6rem and 3.2rem based on the container's inline size. The clamp() function ensures the size never goes below or above the specified bounds, while the middle value (5cqi) makes it fluid.
The cqi and cqb units use logical dimensions, which respect writing mode. In a horizontal writing mode, cqi refers to width and cqb refers to height. In a vertical writing mode, these swap. This makes your components work correctly in different writing systems without additional code.
Media Queries vs Container Queries: When to Use Each
Container queries do not replace media queries. Each has distinct use cases, and understanding when to use each is crucial for building effective responsive designs.
Use Media Queries For:
- Page-level layout changes: Switching from a single-column mobile layout to a multi-column desktop layout
- Navigation patterns: Changing from a hamburger menu to a horizontal navigation bar
- Global typography: Adjusting base font sizes for different screen sizes
- Viewport-dependent features: Showing or hiding elements based on available screen real estate
Use Container Queries For:
- Component adaptation: Making cards, widgets, or modules respond to their container
- Reusable components: Building components that work in different contexts (main content, sidebars, grids)
- Dynamic layouts: When components are placed in containers of varying widths
- Component-specific styling: Adjusting internal component layout without affecting other elements
In practice, you will often use both in the same project. Media queries establish the overall page structure, while container queries handle component-level responsiveness. This combination creates designs that adapt both to the viewport and to the specific context where components are placed.
Seeing Container Queries in Action
Ahmad Shadeed has created an excellent interactive guide to CSS container queries that demonstrates the concept with real, working examples. His demos clearly show the difference between media queries and container queries, including the exact scenarios where media queries fail and container queries succeed. The guide includes interactive examples of news sections, dashboard widgets, social feeds, and more, each with working code and the ability to resize containers to see the behavior in action.
Practical Examples
Container queries shine in real-world scenarios where components need to be flexible. Here are some common patterns where container queries make a significant difference.
Responsive Card Grids
A card grid that uses container queries can adapt regardless of where it is placed:
.card-grid {
container-type: inline-size;
display: grid;
gap: var(--spacing-med);
}
@container (min-width: 600px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@container (min-width: 900px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
}
This grid works equally well in a full-width main content area or a narrow sidebar. The number of columns adjusts based on available space, not viewport width.
Adaptive Form Layouts
Forms can reorganize themselves based on container space:
.form-container {
container-type: inline-size;
}
.form-row {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
@container (min-width: 500px) {
.form-row {
flex-direction: row;
}
.form-row > * {
flex: 1;
}
}
Form fields stack vertically in narrow spaces and arrange horizontally when space allows, making forms usable in any context.
Navigation Components
A navigation component can switch between layouts based on its container:
.nav-container {
container-type: inline-size;
}
.nav {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
@container (min-width: 600px) {
.nav {
flex-direction: row;
justify-content: space-between;
}
}
@container (min-width: 600px) {
.nav-links {
display: flex;
gap: var(--spacing-med);
}
}
This navigation works whether it is in a header, sidebar, or footer. It adapts to the space it has rather than assuming where it will be used.
Browser Support and Fallbacks
Container queries have excellent browser support as of 2024. All major modern browsers support container queries, including Chrome, Edge, Safari, and Firefox. However, the specification is still in Working Draft status, meaning the syntax or behavior could theoretically change, though major changes are unlikely at this stage of implementation.
For projects that must support older browsers, you have several fallback strategies:
Progressive Enhancement
Provide a basic layout that works without container queries, then enhance it for supporting browsers:
/* Base styles work everywhere */
.card {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
/* Enhancement for supporting browsers */
@supports (container-type: inline-size) {
.card-container {
container-type: inline-size;
}
@container (min-width: 500px) {
.card {
flex-direction: row;
}
}
}
The @supports rule checks for container query support before applying the enhanced styles. Browsers without support use the base mobile-first layout, which remains functional.
Hybrid Approach
Combine media queries and container queries for maximum compatibility:
/* Fallback with media queries */
@media (min-width: 768px) {
.card {
flex-direction: row;
}
}
/* Override with container queries where supported */
@supports (container-type: inline-size) {
.card-container {
container-type: inline-size;
}
/* Reset media query styles */
.card {
flex-direction: column;
}
@container (min-width: 500px) {
.card {
flex-direction: row;
}
}
}
This approach provides media query-based responsiveness for older browsers while taking advantage of container queries in modern browsers. The trade-off is slightly more complex CSS, but it ensures broad compatibility.
While container queries are performant, excessive nesting of containers or extremely complex query combinations can impact performance. As with all CSS features, test your implementations and avoid over-engineering. In most cases, container queries perform as well as or better than equivalent JavaScript solutions for component responsiveness.
Container Queries in Practice
To see the ideas from this lesson in action—defining containers, writing @container queries, and building components that respond to their own space—watch the video below. Kevin Powell walks through the same concepts with live examples that reinforce component-level responsiveness.
Key Concepts Summary
Container queries represent a paradigm shift in responsive design. Rather than building components that respond to viewport dimensions, you can now build components that respond to their own available space. This changes how we think about reusability and composition in CSS.
The fundamental concepts to remember are:
- Container queries check the size of a containing element, not the viewport
-
You must explicitly define containers using
container-type - Container query units (cqw, cqh, cqi, cqb) enable fluid sizing relative to containers
- Named containers provide precise control in complex layouts
- Container queries complement media queries rather than replacing them
- Browser support is excellent in modern browsers, with fallback strategies available
As you build components using container queries, you will find that your designs become more flexible and reusable. Components that once required multiple variants or JavaScript solutions can now adapt automatically to their context. This is the power of component-level responsiveness.