UseToolSuite UseToolSuite

CSS Container Queries: The Complete Practical Guide

A practical guide to CSS container queries: defining containment, named containers, CQI/CQW units for fluid typography, and debugging circular-dependency layout bugs.

Necmeddin Cunedioglu Necmeddin Cunedioglu 6 min read
Part of the CSS Color Systems: A Complete Guide for Developers series

Practice what you learn

CSS Minifier

Try it free →

CSS Container Queries: The Complete Practical Guide

For about 15 years, responsive web design has rested on one mechanism: the viewport media query (@media (max-width: 768px)).

Media queries have one blind spot: they only know one thing — how many pixels wide the browser window is.

Here’s where that fails. You design an e-commerce product card that switches from a horizontal layout to a stacked vertical layout below 600px. It works on a phone. But what if a content manager drops that same card into a narrow 300px sidebar on a 4K monitor?

Because the viewport is 3840px wide, your breakpoint never triggers. The card stays horizontal, crushed and overflowing inside the tiny sidebar. It’s broken because it doesn’t know how much space it has — only how much space the browser has.

This is exactly what CSS container queries solve.

As of late 2024, container queries are supported in every major engine (Chrome Blink, Safari WebKit, Firefox Gecko). They’re the biggest shift in frontend layout since CSS Grid.

This guide covers the syntax, the fluid-typography units (CQW/CQI), named container hierarchies, and how to debug the “circular dependency” layout bug.

Minify your CSS. Container queries add new syntax. Keep your production bundles lean by running your stylesheets through our CSS Minifier.

1. The Shift: Component-Level Responsiveness

The syntax difference shows the change in approach:

/* ❌ Legacy: responds to the VIEWPORT */
@media (max-width: 600px) {
  .product-card {
    flex-direction: column;
  }
}

/* ✅ Modern: responds to the PARENT CONTAINER */
@container (max-width: 400px) {
  .product-card {
    flex-direction: column;
  }
}

With container queries, the .product-card becomes self-aware. It doesn’t care whether it’s on an iPhone, an iPad, or a 65-inch TV — only about the size of the element wrapping it.

Drop that card into a full-width grid, a narrow sidebar, or a small modal, and it adapts based on its local surroundings.

2. Step 1: Define the Containment Context

You can’t just write an @container query and expect it to work. You have to tell the browser which parent elements can be measured.

Why? Because layout calculations are expensive. If the browser had to measure every nested element at once, frame rates would drop. You opt in to containment.

.card-wrapper-element {
  /* Track this element's horizontal width */
  container-type: inline-size;
  
  /* Optional but recommended: name the container to avoid nesting conflicts */
  container-name: product-wrapper;
}

The container-type property

There are three values:

  1. inline-size (used 99% of the time): tracks the inline dimension (horizontal width in LTR languages). The height grows and shrinks naturally based on the content inside.
  2. size: tracks both width and height. Warning: if you use this, you must give the container an explicit height (height: 500px or aspect-ratio: 16/9). Otherwise it collapses to 0px and breaks the layout.
  3. normal: the default — no containment.

There’s also a shorthand:

.card-wrapper-element {
  /* Syntax: container: <container-name> / <container-type> */
  container: product-wrapper / inline-size;
}

3. Step 2: Query the Container

Once a parent is a container, you can write queries for the children inside it.

.card {
  display: flex;
  gap: 20px;
}

/* 1. Default state (assumes there's room) */
.card-image-wrapper { width: 50%; }
.card-data-body { width: 50%; }

/* 2. The narrow state */
/* When 'product-wrapper' shrinks below 400px, restack the layout */
@container product-wrapper (max-width: 400px) {
  .card {
    flex-direction: column;
  }
  .card-image-wrapper { width: 100%; }
  .card-data-body { width: 100%; }
}

Nested containers

In a DOM tree with multiple nested containers, a generic @container query measures the nearest ancestor with a container-type. That’s unpredictable in complex React trees, which is why container-name matters — naming lets you target a specific ancestor higher up.

@container main-navigation-sidebar (max-width: 300px) {
  /* Ignores immediate parents and checks the named ancestor */
  .user-profile-avatar {
     display: none;
  }
}

4. Container Query Units (CQW / CQI)

Viewport media queries gave us vw and vh. Those were great for fluid typography, but they had the same viewport-only blind spot.

Container queries add units relative to the container, not the screen.

UnitMeaningUse case
cqw1% of the container’s widthHorizontal fluid scaling.
cqh1% of the container’s heightVertical fluid scaling.
cqi1% of the container’s inline sizeRecommended. Adapts to reading direction (LTR/RTL) automatically.
cqb1% of the container’s block sizeAdapts to the block direction.
cqminThe smaller of cqi or cqbKeeping square elements or icons undistorted.
cqmaxThe larger of cqi or cqbStretching backgrounds across extreme aspect ratios.

Fluid, self-aware typography

Combining cqi with clamp() lets you scale type based on the container size, so text doesn’t overflow in small sidebars.

.dashboard-analytics-widget {
  container-type: inline-size;
}

.widget-metric-title {
  /* clamp(min, fluid, max):
     1. Minimum: 1rem (16px)
     2. Fluid: 5% of the container width (5cqi)
     3. Maximum: 2.5rem (40px) */
  font-size: clamp(1rem, 5cqi, 2.5rem);
}

.widget-internal-padding {
  /* Padding scales down as the widget shrinks */
  padding: clamp(10px, 3cqi, 24px);
}

Drop this widget into a large hero section and the title grows to 40px. Put the same component in a 200px mobile sidebar and it shrinks to 16px — no media queries or JavaScript.

5. Combining CSS Grid and Container Queries

A solid modern pattern uses CSS Grid for the macro layout (the page skeleton) and container queries for the micro layout (the component internals).

/* 1. Macro layout: CSS Grid */
.dashboard-data-grid {
  display: grid;
  /* Fit as many 300px columns as possible */
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 2rem;
}

/* 2. Bridge: make each grid cell a container */
.dashboard-data-grid > div {
  container-type: inline-size;
}

/* 3. Micro layout: container queries */
@container (max-width: 350px) {
  .financial-stat-card {
    /* Stack the icon on top of the number */
    flex-direction: column;
    text-align: center;
  }
}

@container (min-width: 351px) {
  .financial-stat-card {
    /* Place the icon next to the number */
    flex-direction: row;
    justify-content: space-between;
  }
}

This pattern is robust: Grid handles fitting cells into the window, and container queries handle fitting content inside those cells.

6. Debugging Common Bugs

Bug 1: the circular dependency collapse

The most confusing one: you set container-type: size and your component vanishes.

Cause: you created an impossible loop. The parent asks the children how tall they are so it can shrink-wrap them. At the same time, the children query the parent’s height to decide their own. The browser detects the loop, aborts, and collapses the height to 0px.

Fix: if you must use container-type: size, give the container a fixed height (height: 400px or aspect-ratio: 16/9). Otherwise stick to container-type: inline-size.

Bug 2: trying to query the element itself

You can’t put container-type on an element and then use an @container query to style that same element.

/* ❌ This fails silently */
.action-button {
  container-type: inline-size;
}

@container (max-width: 100px) {
  /* The button can't query itself! */
  .action-button { background-color: red; } 
}

An @container query can only style the descendants of the container, never the container itself.

Bug 3: progressive enhancement (the fallback)

Container queries have 95%+ browser support, but regulated apps (banking portals) sometimes have to support old browsers (older Safari on legacy iPads).

Write your base CSS mobile-first, then enhance it with a feature query (@supports).

/* 1. Safe fallback for old browsers */
.complex-card { display: block; }

/* 2. Check if the browser understands container queries */
@supports (container-type: inline-size) {
  
  .card-parent-wrapper { container-type: inline-size; }
  
  /* 3. Apply the modern micro-layout */
  @container (min-width: 500px) {
    .complex-card { display: flex; }
  }
}

Further Reading


Stop writing fragile, viewport-dependent media query hacks. Once your fluid typography is in place, run your stylesheets through our CSS Minifier to strip whitespace and comments. Use our CSS Gradient Generator to build backgrounds that scale with cqi units.

Necmeddin Cunedioglu
Necmeddin Cunedioglu Author
6 min read
-- views

Software developer and the creator of UseToolSuite. I write about the tools and techniques I use daily as a developer — practical guides based on real experience, not theory.