DEV Community

Wilson Xu
Wilson Xu

Posted on

CSS Container Queries: The Layout Revolution You've Been Waiting For

CSS Container Queries: The Layout Revolution You've Been Waiting For

For years, responsive web design meant one thing: media queries. You'd write breakpoints targeting viewport widths, and every component would reflow based on how large the browser window was. It worked well enough — until you started building design systems, reusable components, or complex layouts where the same card component could appear in a narrow sidebar or a wide main content area.

The problem is fundamental. Media queries answer the question "how big is the viewport?" but what you actually need to know is "how big is the container this component lives in?" That's a different question entirely, and until recently, CSS had no answer for it.

CSS Container Queries change that. They let you style elements based on the size of their containing element rather than the viewport. After years of being one of the most requested CSS features, they're now broadly supported and production-ready. This article covers everything you need to know: syntax, practical patterns, real-world migration strategies, and performance considerations.


Why Media Queries Fall Short

Consider a card component. You want it to display as a horizontal layout (image on the left, text on the right) when it has enough space, and as a vertical layout when it's in a narrow container. With media queries, you'd write something like:

.card {
  display: grid;
  grid-template-columns: 1fr;
}

@media (min-width: 600px) {
  .card {
    grid-template-columns: 200px 1fr;
  }
}
Enter fullscreen mode Exit fullscreen mode

This works if the card is always full-width. But drop that card into a sidebar that's 300px wide on a 1200px viewport, and suddenly the viewport breakpoint fires — but the card is cramped. You'd need to override the override, creating a cascade of fragile media query hacks.

This is sometimes called the "component isolation problem." Your component's layout depends on the viewport, but it should depend on its own available space. Every time you reuse a component in a different context, you're fighting against your own breakpoints.

Container Queries solve this at the source.


Container Query Syntax

Container Queries introduce two new concepts: containment and container queries themselves.

Declaring a Container

Before you can query a container, you need to declare it. You do this with the container-type property on the parent element — not the element you're styling:

.card-wrapper {
  container-type: inline-size;
}
Enter fullscreen mode Exit fullscreen mode

The container-type property accepts three values:

  • inline-size — enables queries on the inline axis (width in horizontal writing modes). This is what you'll use most often.
  • block-size — enables queries on the block axis (height).
  • size — enables queries on both axes.

You can also give a container a name, which is essential when you have nested containers or want to target a specific ancestor:

.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

/* Shorthand */
.sidebar {
  container: sidebar / inline-size;
}
Enter fullscreen mode Exit fullscreen mode

Writing a Container Query

The query syntax mirrors @media closely:

@container (min-width: 400px) {
  .card {
    grid-template-columns: 200px 1fr;
  }
}
Enter fullscreen mode Exit fullscreen mode

To target a named container:

@container sidebar (min-width: 300px) {
  .card {
    font-size: 0.875rem;
  }
}
Enter fullscreen mode Exit fullscreen mode

The selector inside the query targets descendants of the container, not the container itself. You cannot style the container element from within its own @container rule — this is by design to avoid circular dependencies.


Size Queries in Depth

Container size queries expose four dimensional values you can query against:

Descriptor Meaning
width The container's width
height The container's height
inline-size Size in the inline direction (width in LTR/RTL)
block-size Size in the block direction (height in horizontal)
aspect-ratio Width divided by height
orientation portrait or landscape

In practice, inline-size is the most broadly useful. It respects writing modes, so your components work correctly whether your users read left-to-right or right-to-left.

.product-grid {
  container-type: inline-size;
}

/* Compact: 1 column */
.product-card {
  grid-column: span 12;
}

/* Medium container: 2 columns */
@container (min-inline-size: 400px) {
  .product-card {
    grid-column: span 6;
  }
}

/* Wide container: 3 columns */
@container (min-inline-size: 700px) {
  .product-card {
    grid-column: span 4;
  }
}
Enter fullscreen mode Exit fullscreen mode

You can also use range syntax (supported in modern browsers alongside container queries):

@container (200px <= inline-size <= 500px) {
  .card__image {
    display: none;
  }
}
Enter fullscreen mode Exit fullscreen mode

Style Queries: Querying CSS Custom Properties

Size queries cover layout concerns, but CSS Container Queries also include style queries — the ability to query the computed value of CSS custom properties on a container.

As of 2024, style queries for custom properties have solid support in Chrome and Safari, with Firefox support shipping. They open up entirely new patterns:

.theme-wrapper {
  --theme: dark;
}

@container style(--theme: dark) {
  .card {
    background: #1a1a2e;
    color: #eee;
  }
}

@container style(--theme: light) {
  .card {
    background: #fff;
    color: #333;
  }
}
Enter fullscreen mode Exit fullscreen mode

This allows contextual theming without class toggling or JavaScript. You set a custom property on a parent, and descendants opt into that context automatically.

A more practical example — a "featured" content variant:

.article-list {
  --featured: false;
}

.article-list .featured-slot {
  --featured: true;
}

@container style(--featured: true) {
  .article-card {
    font-size: 1.25rem;
    padding: 2rem;
  }

  .article-card__image {
    aspect-ratio: 16/9;
  }
}
Enter fullscreen mode Exit fullscreen mode

Style queries let you encode semantic context into the CSS layer rather than proliferating modifier classes.


Practical Patterns

The Self-Aware Card Component

This is the canonical container queries use case. A single .card component adapts to wherever it's placed:

.card-container {
  container-type: inline-size;
}

.card {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

.card__image {
  aspect-ratio: 16/9;
  object-fit: cover;
  border-radius: 8px 8px 0 0;
}

/* When the container is wide enough for a horizontal layout */
@container (min-inline-size: 480px) {
  .card {
    grid-template-columns: 200px 1fr;
    grid-template-rows: auto;
  }

  .card__image {
    aspect-ratio: 1;
    border-radius: 8px 0 0 8px;
  }
}

/* When the container is very wide — magazine layout */
@container (min-inline-size: 700px) {
  .card {
    grid-template-columns: 320px 1fr;
    gap: 2rem;
  }

  .card__title {
    font-size: 1.75rem;
  }
}
Enter fullscreen mode Exit fullscreen mode

Place this card in a 280px sidebar, a 600px two-column main, and a 900px hero section — it adapts correctly in each context without a single viewport media query.

Sidebar Layout That Actually Works

The classic "holy grail" sidebar layout has always been tricky. With container queries, sidebars become first-class layout citizens:

.page-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
}

@media (min-width: 900px) {
  .page-layout {
    grid-template-columns: 280px 1fr;
  }
}

.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

.main-content {
  container-type: inline-size;
  container-name: main;
}

/* Sidebar widgets collapse their internal layouts */
@container sidebar (max-inline-size: 260px) {
  .widget-list {
    flex-direction: column;
  }

  .widget-list__item {
    font-size: 0.875rem;
  }
}

/* Main content can run multi-column article grids */
@container main (min-inline-size: 600px) {
  .article-grid {
    columns: 2;
    column-gap: 2rem;
  }
}
Enter fullscreen mode Exit fullscreen mode

The viewport media query only handles the structural split. Container queries handle all the component-level adaptation inside each region.

Responsive Typography Without JavaScript

Container queries enable fluid typography that responds to container size rather than viewport:

.text-block {
  container-type: inline-size;
}

.text-block h2 {
  font-size: 1.25rem;
  line-height: 1.3;
}

@container (min-inline-size: 400px) {
  .text-block h2 {
    font-size: 1.75rem;
  }
}

@container (min-inline-size: 600px) {
  .text-block h2 {
    font-size: 2.25rem;
    line-height: 1.2;
  }
}
Enter fullscreen mode Exit fullscreen mode

Combine this with cqi units — the container query equivalent of viewport units:

.headline {
  /* 1cqi = 1% of the container's inline size */
  font-size: clamp(1rem, 4cqi, 3rem);
}
Enter fullscreen mode Exit fullscreen mode

cqi (container query inline size), cqb (block size), cqw, cqh, cqmin, and cqmax are all valid container query length units. They work exactly like vw/vh but scoped to the nearest sized container.

Navigation That Collapses Intelligently

A navigation component that adapts based on available space, not the viewport:

.nav-wrapper {
  container-type: inline-size;
}

.nav {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.nav__item {
  white-space: nowrap;
}

/* When the nav container shrinks, switch to icon-only mode */
@container (max-inline-size: 500px) {
  .nav__item span {
    display: none;
  }

  .nav__item svg {
    display: block;
  }
}

/* Ultra-narrow: hamburger mode */
@container (max-inline-size: 280px) {
  .nav {
    flex-direction: column;
    position: absolute;
    /* dropdown behavior */
  }
}
Enter fullscreen mode Exit fullscreen mode

Combining Container Queries with CSS Grid and Flexbox

Container queries and intrinsic layout techniques like Grid and Flexbox are complementary, not competing. The most powerful patterns use them together.

Auto-Responsive Grid Without Breakpoints

.grid-container {
  container-type: inline-size;
}

.auto-grid {
  display: grid;
  gap: 1.5rem;
  /* Default: 1 column */
  grid-template-columns: 1fr;
}

@container (min-inline-size: 400px) {
  .auto-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@container (min-inline-size: 700px) {
  .auto-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@container (min-inline-size: 1000px) {
  .auto-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
Enter fullscreen mode Exit fullscreen mode

Because the query fires based on the container's width, this grid will always show the right number of columns — whether it's in a full-width section or nested inside a two-column layout.

Flexbox with Container-Aware Wrapping

.feature-list {
  container-type: inline-size;
}

.features {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.feature-item {
  flex: 1 1 100%;
}

@container (min-inline-size: 500px) {
  .feature-item {
    flex: 1 1 calc(50% - 0.5rem);
  }
}

@container (min-inline-size: 800px) {
  .feature-item {
    flex: 1 1 calc(33.33% - 0.67rem);
  }
}
Enter fullscreen mode Exit fullscreen mode

Browser Support

Container Queries are now broadly supported across all major browsers:

Browser Version Added Notes
Chrome 105 (Aug 2022) Full size query support
Edge 105 (Aug 2022) Chromium-based, same as Chrome
Firefox 110 (Feb 2023) Full size query support
Safari 16.0 (Sep 2022) Full size query support
Samsung Internet 20 (Dec 2022) Based on Chromium
Opera 91 (Sep 2022) Chromium-based

Global support as of early 2025: ~94% (Can I Use data).

Style queries for custom properties have slightly narrower support:

Browser Status
Chrome 111+ Supported
Safari 18+ Supported
Firefox In development (flag available)

Polyfills

For projects that need to support older browsers:

@oddbird/css-container-queries-polyfill is the most maintained option. It uses a MutationObserver and ResizeObserver under the hood:

npm install @oddbird/css-container-queries-polyfill
Enter fullscreen mode Exit fullscreen mode
import '@oddbird/css-container-queries-polyfill';
Enter fullscreen mode Exit fullscreen mode

The polyfill requires containers to have explicit contain: layout style (which container-type implies in spec browsers). It handles most size query use cases but has known limitations with nested named containers.

For new projects with a modern browser baseline (last 2 major versions), you can ship container queries without a polyfill today. For legacy support requirements, test the polyfill against your specific patterns.


Performance Considerations

Container queries are implemented efficiently in modern browsers, but there are patterns to avoid.

Containment Has Side Effects

container-type: size (both axes) implicitly applies contain: size to the element, which means the browser treats it as having no intrinsic size from its contents. This can break layouts where the container's height should be determined by its children.

Prefer container-type: inline-size unless you genuinely need block-size queries. It applies contain: inline-size which is far less likely to cause layout surprises.

Avoid Deeply Nested Containers

Every sized container triggers its own layout containment boundary. Deeply nested stacks of containers can add layout calculation overhead, especially with size containment. In practice, this matters at dozens of nested containers — a few levels deep is fine.

cqi Units and Reflows

cqi/cqb units recalculate on every container resize. If you're animating a container's size (e.g., an expanding sidebar), text sized with cqi will reflow on every animation frame. Prefer fixed units inside animations, or use will-change: transform to promote the animated element and reduce reflow scope.

No Container Queries on the Container Itself

As mentioned, you cannot query a container's own styles from within its own @container rule. This is intentional: circular style queries would require multiple layout passes. The browser resolves containment top-down, and this constraint keeps that resolution O(n).


Migrating From Media Queries

Container queries don't replace media queries — they complement them. A practical migration strategy:

Step 1: Identify Reused Components

Audit your codebase for components that appear in multiple contexts with different layouts. Cards, navigation items, widgets, and grid cells are prime candidates.

Step 2: Add Containment to Wrappers

Don't add container-type to the component itself — add it to the wrapper or parent:

/* Before: component targets viewport */
.card { /* styles */ }
@media (min-width: 600px) { .card { /* wider styles */ } }

/* After: wrapper establishes containment */
.card-wrapper {
  container-type: inline-size;
}

.card { /* default styles */ }

@container (min-inline-size: 400px) {
  .card { /* wider styles */ }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Keep Structural Media Queries

Continue using viewport media queries for page-level structural decisions: showing/hiding sidebars, switching from single to multi-column layouts, adjusting global spacing. Container queries handle component-level adaptation inside those structures.

/* Media query: structural */
@media (min-width: 1024px) {
  .page {
    grid-template-columns: 280px 1fr;
  }
}

/* Container query: component-level */
@container (min-inline-size: 500px) {
  .article-card {
    grid-template-columns: 160px 1fr;
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Test in Context

The power of container queries shows up when you move components into different contexts. After migration, test each component by placing it in narrow and wide containers. The easiest way is to wrap a test instance in a container with a fixed width:

<div style="container-type: inline-size; width: 300px;">
  <article-card></article-card>
</div>

<div style="container-type: inline-size; width: 700px;">
  <article-card></article-card>
</div>
Enter fullscreen mode Exit fullscreen mode

A Note on Design System Architecture

Container queries are transformative for design systems. Components can now be truly self-contained — they carry their responsive behavior with them. When you ship a component library, consumers don't need to know anything about breakpoints. They drop the component in, wrap it in a sized container, and it adapts automatically.

This inverts the old model. Previously, responsive design was a concern of the page (viewport breakpoints defined how every component behaved). With container queries, responsive design is a concern of the component. Pages just decide how much space to give each component. The components handle the rest.


What's Coming Next

The Container Queries spec continues to evolve. A few developments worth watching:

State queries are being discussed for querying element states (:hover, :focus, :checked) on a container. This would enable even more powerful contextual styling.

Style query expansion — beyond custom properties, future iterations may allow querying standard CSS property values on containers, though this is still in early discussion due to complexity.

Scroll-state container queries are already shipping in Chrome: query whether a container is currently snapped, stuck (sticky positioning), or overflowing. This enables scroll-aware UI without JavaScript:

@container scroll-state(snapped: x) {
  .slide {
    opacity: 1;
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

CSS Container Queries are not just a new feature — they're a new way of thinking about responsive design. The shift from "how big is the viewport?" to "how much space does this component have?" is subtle in description but profound in practice.

The practical wins are immediate: component reuse becomes genuinely straightforward, design systems stop fighting their own breakpoints, and the classic "same card, different context" problem disappears.

With ~94% global browser support and no need for polyfills in most modern projects, the barrier to adoption is low. The migration path is gradual — you don't need to rewrite everything at once. Start with your most reused components, add container-type: inline-size to their wrappers, and replace viewport breakpoints with container queries one component at a time.

The viewport media query isn't going anywhere. But for components, containers are the right model. Start using them.


Browser support data sourced from MDN and Can I Use (March 2025). Code examples tested in Chrome 121, Firefox 122, and Safari 17.3.

Top comments (0)