Container Queries
What Are Container Queries and Why They Exist
Container queries are one of the most significant additions to CSS in recent years. They allow you to style an element based on the size of its parent container rather than the size of the viewport. This might sound like a small distinction, but it fundamentally changes how we approach responsive design. Before container queries, every responsive decision you made was based on how wide or tall the browser window was. With container queries, you can make responsive decisions based on how much space a component actually has available to it -- enabling truly reusable, self-contained responsive components.
Consider a card component that appears in three different places on your website: in a wide main content area, in a narrow sidebar, and in a grid that changes column count at different breakpoints. With traditional media queries, you would need to know exactly where that card lives in the page layout and write specific media queries for each context. If the layout changes -- say the sidebar moves from the right to the bottom on mobile -- your card-specific media queries might break. Container queries solve this by letting the card respond to its own container's size, regardless of where it is placed in the page.
The Limitation of Media Queries
Media queries have been the cornerstone of responsive web design since Ethan Marcotte popularized the concept in 2010. They work by querying characteristics of the viewport -- primarily its width and height. When you write @media (min-width: 768px), you are asking the browser whether the viewport is at least 768 pixels wide, and if so, applying a set of styles.
This viewport-based approach works well for page-level layout decisions: how many columns should the main grid have, should the navigation be a hamburger menu or a horizontal bar, should the hero section stack vertically or sit side by side. These are inherently viewport-level concerns.
However, media queries fall short when it comes to component-level responsiveness. Here is why:
- Components do not know their own size: A media query tells you the viewport width, but a component inside a sidebar might only occupy 300 pixels of a 1200-pixel-wide viewport. The component has no way of knowing its own available width using media queries alone.
- Layout context changes: The same component might appear in a full-width area on one page and a narrow column on another. Media queries cannot account for this without duplicating or complicating your CSS.
- Reusability suffers: When component styles are tied to viewport breakpoints, moving that component to a different layout context requires rewriting its responsive styles.
- Design systems break down: In a design system or component library, each component should be self-contained. Viewport-dependent responsive styles leak layout assumptions into components.
The Problem: Media Queries Cannot Adapt to Container Size
/* This card responds to the VIEWPORT width, not its container */
.card {
display: flex;
flex-direction: column;
}
@media (min-width: 768px) {
.card {
flex-direction: row;
}
}
/* Problem: If this card is placed in a 300px sidebar on a 1200px screen,
the media query still triggers at 768px viewport width,
making the card horizontal even though it only has 300px of space.
The card layout is broken because it does not know its own context. */
Defining a Containment Context
Before you can use container queries, you must first establish which elements act as containers. This is done using the container-type property. By default, no element is a query container -- you must explicitly opt in. This is because container queries require the browser to track the size of the container independently of its children, which has performance implications. The browser needs to establish a containment context to avoid circular dependencies where a child's style depends on the parent's size, which in turn depends on the child's size.
container-type
The container-type property accepts three values:
inline-size-- Establishes a query container for inline-size (width in horizontal writing modes) queries. This is the most commonly used value because the vast majority of responsive design decisions are based on available width. The element gains inline-size containment, meaning its inline size is determined independently of its children.size-- Establishes a query container for both inline-size and block-size (width and height) queries. Use this when you need to query both dimensions. Note that this also means the element's height must be explicitly set or determined by something other than its content, because block-size containment prevents the element from sizing based on its children's height.normal-- The default value. The element is not a query container for size queries, though it can still be used with container style queries (a future CSS feature).
Setting Up a Container
/* Most common: query based on width only */
.card-wrapper {
container-type: inline-size;
}
/* Query based on both width and height */
.dashboard-panel {
container-type: size;
height: 400px; /* Required! Block-size containment needs explicit height */
}
/* Not a container (default behavior) */
.regular-div {
container-type: normal;
}
container-type: size, the element cannot derive its height from its content. You must set an explicit height, min-height, or have the height determined by an external layout mechanism (such as being a flex or grid child with a stretch alignment). Without an explicit height, the element will collapse to zero height because block-size containment prevents it from using its children to determine its own height.container-name
When you have multiple nested containers, you might want a child element to respond to a specific ancestor container rather than the nearest one. The container-name property lets you assign one or more names to a container, which you can then reference in your @container rules.
Naming Containers
/* Name your containers for precise targeting */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
.main-content {
container-type: inline-size;
container-name: main;
}
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Multiple names on one container */
.dashboard-widget {
container-type: inline-size;
container-name: widget panel;
/* This container can be referenced by either "widget" or "panel" */
}
@container rules to target that specific container.The container Shorthand
The container shorthand property combines container-name and container-type into a single declaration. The syntax is container: <name> / <type>.
Using the container Shorthand
/* Shorthand: name / type */
.sidebar {
container: sidebar / inline-size;
}
/* Equivalent longhand */
.sidebar {
container-name: sidebar;
container-type: inline-size;
}
/* Multiple names with shorthand */
.widget {
container: widget panel / inline-size;
}
/* Type only (no name) */
.wrapper {
container: / inline-size;
/* Or simply use container-type: inline-size; */
}
Writing Container Queries with @container
Once you have established containment contexts, you can write container queries using the @container at-rule. The syntax is similar to media queries but targets container size instead of viewport size.
Basic @container Syntax
/* Query the nearest ancestor container */
@container (min-width: 400px) {
.card {
flex-direction: row;
}
}
/* Query a specific named container */
@container sidebar (max-width: 300px) {
.nav-link {
font-size: 0.875rem;
padding: 4px 8px;
}
}
/* Combine conditions */
@container card (min-width: 500px) and (max-width: 800px) {
.card-title {
font-size: 1.5rem;
}
}
/* Width range syntax (similar to media query range syntax) */
@container (400px <= width <= 800px) {
.card {
grid-template-columns: 1fr 2fr;
}
}
When you write @container (min-width: 400px) without specifying a name, the query targets the nearest ancestor that has container-type set to inline-size or size. When you write @container sidebar (min-width: 400px), it specifically targets the nearest ancestor container named "sidebar", skipping over any unnamed containers or containers with different names.
Container Query Length Units
Container queries introduce a new set of length units that are relative to the size of the query container. These are analogous to viewport units (vw, vh) but relative to the container instead of the viewport.
cqw-- 1% of the query container's width.cqh-- 1% of the query container's height.cqi-- 1% of the query container's inline size (width in horizontal writing modes).cqb-- 1% of the query container's block size (height in horizontal writing modes).cqmin-- The smaller value ofcqiandcqb.cqmax-- The larger value ofcqiandcqb.
Using Container Query Units
.card-wrapper {
container: card / inline-size;
}
.card-title {
/* Font size scales with the container width */
font-size: clamp(1rem, 4cqi, 2rem);
}
.card-image {
/* Image takes 40% of the container width */
width: 40cqi;
height: auto;
}
.hero-text {
/* Use cqmin for consistent scaling regardless of orientation */
font-size: 5cqmin;
padding: 2cqi;
}
/* Container units work in any CSS property that accepts lengths */
.responsive-padding {
padding: 3cqi 5cqi;
margin-bottom: 2cqb;
border-radius: 1cqi;
}
cqh and cqb require the container to have container-type: size (not just inline-size) because the container must track its block size as well.Building Truly Reusable Responsive Components
The real power of container queries emerges when you build components that adapt entirely based on their container size. Let us build a complete responsive card component that works perfectly whether it is placed in a wide content area, a narrow sidebar, or anything in between.
Example: A Fully Responsive Card Component
<style>
/* Step 1: Establish the containment context on the card wrapper */
.card-container {
container: card / inline-size;
}
/* Step 2: Base styles -- mobile-first, vertical layout */
.responsive-card {
display: grid;
grid-template-columns: 1fr;
gap: 0;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
background: white;
}
.responsive-card .card-image img {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
}
.responsive-card .card-body {
padding: 16px;
}
.responsive-card .card-title {
font-size: 1.125rem;
margin: 0 0 8px 0;
}
.responsive-card .card-text {
font-size: 0.875rem;
color: #666;
line-height: 1.5;
}
.responsive-card .card-meta {
display: none;
}
/* Step 3: When the container is at least 400px wide, go horizontal */
@container card (min-width: 400px) {
.responsive-card {
grid-template-columns: 200px 1fr;
}
.responsive-card .card-image img {
height: 100%;
min-height: 180px;
}
.responsive-card .card-body {
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
}
.responsive-card .card-title {
font-size: 1.25rem;
}
}
/* Step 4: When the container is at least 600px, show more detail */
@container card (min-width: 600px) {
.responsive-card {
grid-template-columns: 280px 1fr;
}
.responsive-card .card-body {
padding: 24px;
}
.responsive-card .card-title {
font-size: 1.5rem;
margin-bottom: 12px;
}
.responsive-card .card-text {
font-size: 1rem;
}
.responsive-card .card-meta {
display: flex;
gap: 16px;
margin-top: 16px;
font-size: 0.875rem;
color: #999;
}
}
</style>
<!-- This card adapts to ANY container width -->
<div class="card-container">
<article class="responsive-card">
<div class="card-image">
<img src="photo.jpg" alt="Article image">
</div>
<div class="card-body">
<h3 class="card-title">Building Responsive Components</h3>
<p class="card-text">Container queries allow components to
respond to their available space rather than the viewport.</p>
<div class="card-meta">
<span>5 min read</span>
<span>Published today</span>
</div>
</div>
</article>
</div>
The beauty of this approach is that the same card component works correctly in any context without modification. Place it in a full-width area and it shows the expanded horizontal layout with metadata. Place it in a narrow sidebar and it collapses to a compact vertical layout. No media queries needed, no layout-specific overrides, and no JavaScript to measure container widths.
Practical Example: Responsive Sidebar Widget
Let us build a sidebar widget that adapts its layout based on the sidebar's width. This is a perfect use case because sidebars often change width or collapse entirely on different screen sizes.
Example: Adaptive Sidebar Widget
<style>
.sidebar {
container: sidebar / inline-size;
width: 100%;
}
.widget {
background: #f8f9fa;
border-radius: 8px;
padding: 3cqi;
}
.widget-title {
font-size: clamp(0.875rem, 3cqi, 1.25rem);
font-weight: 700;
margin-bottom: 12px;
border-bottom: 2px solid #e9ecef;
padding-bottom: 8px;
}
.widget-list {
list-style: none;
padding: 0;
margin: 0;
}
.widget-list li {
padding: 8px 0;
border-bottom: 1px solid #e9ecef;
display: flex;
align-items: center;
gap: 8px;
}
.widget-list .item-icon {
width: 32px;
height: 32px;
border-radius: 50%;
background: #dee2e6;
flex-shrink: 0;
}
.widget-list .item-details {
flex: 1;
min-width: 0;
}
.widget-list .item-name {
font-weight: 600;
font-size: 0.875rem;
}
.widget-list .item-meta {
font-size: 0.75rem;
color: #6c757d;
display: none;
}
/* When sidebar is wide enough, show metadata */
@container sidebar (min-width: 250px) {
.widget-list .item-meta {
display: block;
}
}
/* When sidebar is very wide, switch to grid layout */
@container sidebar (min-width: 400px) {
.widget-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.widget-list li {
flex-direction: column;
text-align: center;
padding: 12px;
border-bottom: none;
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.widget-list .item-icon {
width: 48px;
height: 48px;
}
}
</style>
<aside class="sidebar">
<div class="widget">
<h3 class="widget-title">Popular Authors</h3>
<ul class="widget-list">
<li>
<div class="item-icon"></div>
<div class="item-details">
<div class="item-name">Jane Smith</div>
<div class="item-meta">42 articles</div>
</div>
</li>
<li>
<div class="item-icon"></div>
<div class="item-details">
<div class="item-name">John Doe</div>
<div class="item-meta">38 articles</div>
</div>
</li>
</ul>
</div>
</aside>
Container Queries vs Media Queries: When to Use Each
Container queries do not replace media queries. They serve different purposes and work best when used together. Understanding when to reach for each one is critical for writing maintainable CSS.
Use media queries when:
- You are making page-level layout decisions (number of columns in the main grid, navigation style, overall page structure).
- You need to respond to viewport characteristics beyond size, such as
prefers-color-scheme,prefers-reduced-motion,orientation, orhovercapabilities. - You are setting global typography scales or spacing systems based on screen size.
- You need to target specific device categories (print styles, screen styles).
- You are working with older browsers that do not support container queries.
Use container queries when:
- You are styling a reusable component that might appear in different layout contexts.
- A component's layout should depend on its available space rather than the overall viewport.
- You are building a component library or design system where components must be self-contained.
- You have a component that appears in both a sidebar and a main content area with different widths.
- You want your components to automatically adapt when the page layout changes around them.
Combining Media Queries and Container Queries
/* Media query for page-level layout */
@media (min-width: 768px) {
.page-layout {
display: grid;
grid-template-columns: 300px 1fr;
gap: 24px;
}
}
@media (min-width: 1200px) {
.page-layout {
grid-template-columns: 350px 1fr 300px;
}
}
/* Container queries for component-level responsiveness */
.content-area {
container: content / inline-size;
}
.sidebar {
container: sidebar / inline-size;
}
/* The same card component adapts to whichever container it is in */
@container (min-width: 400px) {
.article-card {
display: grid;
grid-template-columns: 150px 1fr;
gap: 16px;
}
}
@container (min-width: 600px) {
.article-card {
grid-template-columns: 250px 1fr;
}
.article-card .card-excerpt {
display: block;
}
}
Browser Support and Progressive Enhancement
Container queries have excellent browser support in all modern browsers. Chrome, Edge, Firefox, and Safari all support container queries from their 2023 releases onward. However, if you need to support older browser versions, you should implement container queries as a progressive enhancement.
The @supports rule can detect container query support:
Progressive Enhancement with @supports
/* Base styles that work everywhere */
.card {
display: flex;
flex-direction: column;
}
/* Media query fallback for older browsers */
@media (min-width: 600px) {
.card {
flex-direction: row;
}
}
/* Enhanced container query styles for modern browsers */
@supports (container-type: inline-size) {
.card-wrapper {
container: card / inline-size;
}
/* Reset the media query override */
.card {
flex-direction: column;
}
/* Use container queries instead */
@container card (min-width: 400px) {
.card {
flex-direction: row;
}
}
}
/* You can also check for @container support directly */
@supports (container-name: test) {
/* Container query features are supported */
}
@supports. In modern browsers, the container queries take over and provide more granular responsiveness. In older browsers, the media query fallbacks still provide a good responsive experience.Nested Containers
You can nest containers inside other containers. When a child element has a @container query without a name, it resolves against the nearest ancestor container. When it has a named query, it resolves against the nearest ancestor with that specific name.
Working with Nested Containers
<style>
.outer {
container: outer / inline-size;
padding: 20px;
background: #f0f0f0;
}
.inner {
container: inner / inline-size;
padding: 16px;
background: #e0e0e0;
}
/* Targets nearest container (inner) */
@container (min-width: 300px) {
.content {
font-size: 1.125rem;
}
}
/* Specifically targets the outer container */
@container outer (min-width: 600px) {
.content {
color: darkblue;
}
}
/* Specifically targets the inner container */
@container inner (min-width: 250px) {
.content {
font-weight: bold;
}
}
</style>
<div class="outer">
<div class="inner">
<p class="content">This text responds to both containers.</p>
</div>
</div>
Common Patterns and Best Practices
As container queries become more widely adopted, several patterns and best practices have emerged that help you use them effectively.
1. Wrap components in container elements: Rather than making the component itself a container (which can cause issues because an element cannot query its own size), wrap it in a dedicated container element.
Container Wrapper Pattern
/* Correct: Separate wrapper and component */
.card-container {
container: card / inline-size;
}
.card {
/* Component styles */
}
@container card (min-width: 400px) {
.card { /* ... */ }
}
/* Incorrect: Element querying its own size */
/* .card {
container-type: inline-size;
}
@container (min-width: 400px) {
.card { ... } -- This queries the PARENT container, not .card itself
} */
2. Use container query units for fluid typography: Container query units are excellent for creating typography that scales smoothly with the container size.
Fluid Typography with Container Units
.text-wrapper {
container: text / inline-size;
}
.headline {
/* Fluid font size between 1.25rem and 3rem based on container width */
font-size: clamp(1.25rem, 5cqi, 3rem);
line-height: 1.2;
}
.body-text {
/* Fluid font size between 0.875rem and 1.125rem */
font-size: clamp(0.875rem, 2cqi, 1.125rem);
line-height: 1.6;
}
.caption {
font-size: clamp(0.75rem, 1.5cqi, 0.875rem);
}
3. Create container-aware spacing: Use container query units for padding and margins that adapt to the container width.
Responsive Spacing with Container Units
.section-wrapper {
container: section / inline-size;
}
.section-content {
padding: clamp(16px, 4cqi, 48px);
}
.section-content h2 {
margin-bottom: clamp(12px, 2cqi, 32px);
}
.section-content .grid {
gap: clamp(8px, 2cqi, 24px);
}
The Future of Component-Based Responsive Design
Container queries represent a paradigm shift in how we think about responsive design. Instead of a top-down approach where the page layout dictates component behavior, we now have a bottom-up approach where components are self-aware and self-adapting. This aligns perfectly with the component-based architecture used by modern frameworks like React, Vue, Angular, and Web Components.
Looking ahead, the CSS specification is expanding container queries to include style queries. Style queries will allow you to query the computed value of custom properties (CSS variables) on a container, enabling even more powerful conditional styling patterns. For example, you might query whether a container has a dark or light theme applied and style children accordingly, without relying on class names or data attributes.
Future: Style Queries (Experimental)
/* Style queries -- coming soon to browsers */
.theme-container {
--theme: dark;
}
/* Query a custom property value on the container */
@container style(--theme: dark) {
.card {
background: #1a1a2e;
color: #eee;
}
}
@container style(--theme: light) {
.card {
background: #ffffff;
color: #333;
}
}
Container queries, combined with modern CSS features like :has(), cascade layers, and subgrid, are moving CSS toward a future where components are truly self-contained, portable, and adaptive. The days of fighting with viewport-based breakpoints for component-level responsiveness are ending.
Exercise 1: Responsive Card Gallery
Create a card gallery that uses container queries to adapt each card's layout. Build a page with a main content area and a sidebar. Use CSS Grid or Flexbox with media queries for the page-level layout (sidebar on the left, main content on the right, stacking vertically on small screens). Create a reusable card component that shows a vertical layout (image on top, content below) when its container is narrow, switches to a horizontal layout (image on left, content on right) when the container is at least 400 pixels wide, and expands to show additional metadata (author, date, read time) when the container is at least 600 pixels wide. Place the same card component in both the main content area and the sidebar. Verify that the cards adapt independently to their respective containers rather than the viewport. Use container query units for the card's internal spacing and font sizes so they scale fluidly. Test by resizing the browser window and observe how cards in different containers respond differently at the same viewport width.
Exercise 2: Dashboard with Container-Aware Widgets
Build a dashboard layout with multiple resizable panels, each containing widgets that adapt to their panel size using container queries. Create a dashboard grid with at least four panels. Use CSS Grid for the dashboard layout and allow columns to be resized (you can simulate different sizes by changing grid-template-columns values or by placing panels in columns of different widths). Inside each panel, create a statistics widget that displays as a compact single-line layout when the panel is narrow (under 200 pixels), expands to show a small chart placeholder and trend indicator when the panel is at least 300 pixels wide, and shows a full data table below the chart when the panel is at least 500 pixels wide. Use named containers for the panels and write container queries that target them by name. Add container query units for responsive typography within the widgets. Implement a progressive enhancement strategy using @supports so that browsers without container query support still see a reasonable layout using media query fallbacks. Include at least one example of nested containers where a widget inside a panel is itself a container for sub-components.