CSS

CSS Container Queries Explained: Build Smarter Responsive Components

W
W3Tweaks Team
Frontend Tutorials
May 22, 2026 8 min read
CSS Container Queries Explained: Build Smarter Responsive Components
Media queries respond to the viewport. Container queries respond to the parent element. This single difference unlocks truly reusable components that adapt to wherever they are placed — sidebars, grids, modals, anywhere.

For years, CSS developers faced an awkward problem: a card component in a wide main column needs three columns of content, but the same card in a narrow sidebar needs a single stacked column. Media queries could not help — they only know about the viewport width, not where the component actually lives on the page.

Container queries solve this. They let a component respond to the size of its parent container rather than the browser window. The same card can be wide in a large container and compact in a small one — with zero JavaScript and zero media query hacks.

Container queries are now Baseline — supported in all modern browsers since late 2023. They are production-ready and actively changing how developers write CSS.

How Container Queries Work

The concept is simple: you declare an element as a containment context, then write queries that fire based on that container’s size.

/* Step 1: Declare a containment context */
.card-wrapper {
  container-type: inline-size;
}

/* Step 2: Query the container inside the component */
@container (min-width: 400px) {
  .card {
    display: flex;
    flex-direction: row;
  }
}

When .card-wrapper is wider than 400px, the card lays out horizontally. When it is narrower, the card stacks. This works no matter where .card-wrapper appears on the page.

The Three container-type Values

/* Responds to inline-size (width) only — most common */
.wrapper { container-type: inline-size; }

/* Responds to both width AND height — use sparingly */
.wrapper { container-type: size; }

/* Creates a containment context but no queries — rarely needed */
.wrapper { container-type: normal; }

inline-size is what you will use 95% of the time. It enables width-based queries without the performance cost of height containment.

Naming Containers

When you have nested containers, name them to target the right one:

.page-layout {
  container-type: inline-size;
  container-name: layout;
}

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

/* Targets the sidebar container specifically */
@container sidebar (min-width: 280px) {
  .widget { flex-direction: row; }
}

/* Targets the page layout container */
@container layout (min-width: 900px) {
  .main-content { columns: 2; }
}

/* Shorthand */
.sidebar {
  container: sidebar / inline-size;
}

Your First Real Component

A post card that adapts between a compact stacked layout and a wide horizontal layout:

<div class="card-container">
  <div class="post-card">
    <div class="card-image">
      <img src="thumbnail.jpg" alt="Post thumbnail">
    </div>
    <div class="card-body">
      <span class="card-tag">CSS</span>
      <h2 class="card-title">CSS Grid and Flexbox: Complete Guide</h2>
      <p class="card-excerpt">Everything you need to know about modern CSS layout.</p>
      <div class="card-meta">
        <span class="card-author">W3Tweaks Team</span>
        <span class="card-date">May 22, 2026</span>
      </div>
    </div>
  </div>
</div>
/* Containment context on the wrapper */
.card-container {
  container-type: inline-size;
}

/* Default: stacked (mobile-first) */
.post-card {
  display: flex;
  flex-direction: column;
  background: #161c2d;
  border: 1px solid rgba(255,255,255,.08);
  border-radius: 12px;
  overflow: hidden;
}

.card-image img {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.card-body {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.card-excerpt { display: none; }

/* When container is wide enough: go horizontal */
@container (min-width: 480px) {
  .post-card {
    flex-direction: row;
  }

  .card-image {
    width: 200px;
    flex-shrink: 0;
  }

  .card-image img {
    width: 100%;
    height: 100%;
  }

  .card-excerpt {
    display: block;
  }
}

/* When container is really wide: three-column body */
@container (min-width: 700px) {
  .card-image { width: 280px; }

  .card-body {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto 1fr auto;
    gap: 8px 16px;
  }

  .card-tag  { grid-column: 1; grid-row: 1; }
  .card-title{ grid-column: 1 / -1; grid-row: 2; font-size: 22px; }
  .card-excerpt { grid-column: 1; grid-row: 3; }
  .card-meta { grid-column: 2; grid-row: 1; text-align: right; }
}

Now place .card-container anywhere — in a full-width section, a 300px sidebar, or a modal — and the card adapts automatically.

Container Query Units

Container queries introduce four new CSS units that are relative to the container size:

@container (min-width: 400px) {
  .card-title {
    /* cqw — 1% of container width */
    font-size: clamp(16px, 4cqw, 28px);

    /* cqh — 1% of container height */
    min-height: 10cqh;

    /* cqi — 1% of container inline size */
    padding: 2cqi;

    /* cqb — 1% of container block size */
    margin-bottom: 1cqb;

    /* cqmin / cqmax — smaller/larger of cqi and cqb */
    border-radius: 1cqmin;
  }
}

cqw and cqi are the most useful — they let you scale typography and spacing proportionally to the container, not the viewport. This is the CSS equivalent of fluid typography but component-scoped.

Practical Use Case: Navigation Component

A navigation bar that collapses to icons-only when in a narrow container:

.nav-container {
  container: nav / inline-size;
}

.nav-list {
  display: flex;
  align-items: center;
  gap: 4px;
  list-style: none;
}

.nav-item a {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-radius: 8px;
  color: #7a90b8;
  text-decoration: none;
  transition: all .15s;
}

.nav-item a:hover {
  background: rgba(255,255,255,.07);
  color: #f0f6ff;
}

/* Hide text labels in narrow containers */
@container nav (max-width: 200px) {
  .nav-label { display: none; }
  .nav-item a { padding: 8px; justify-content: center; }
}

/* Show labels and arrange horizontally in wider containers */
@container nav (min-width: 201px) {
  .nav-label { display: inline; }
}

The same nav component works in a collapsed sidebar, a wide header, or a mobile drawer — all without media queries.

Practical Use Case: Data Dashboard Widget

A stats widget that rearranges based on available space:

.widget-container {
  container: widget / inline-size;
}

.stats-widget {
  background: #161c2d;
  border: 1px solid rgba(255,255,255,.08);
  border-radius: 12px;
  padding: 16px;
}

/* Compact: number above label */
.stat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 4px;
}

.stat-number {
  font-size: 2rem;
  font-weight: 800;
  color: #f0f6ff;
}

.stat-label {
  font-size: 0.75rem;
  color: #546e8a;
  text-transform: uppercase;
  letter-spacing: .08em;
}

/* Medium widget: show multiple stats in a row */
@container widget (min-width: 320px) {
  .stats-widget {
    display: flex;
    gap: 0;
  }

  .stat-item {
    flex: 1;
    border-right: 1px solid rgba(255,255,255,.06);
    padding: 0 16px;
  }

  .stat-item:last-child { border-right: none; }
}

/* Large widget: inline number + label + trend */
@container widget (min-width: 520px) {
  .stats-widget { gap: 0; }

  .stat-item {
    flex-direction: row;
    align-items: center;
    text-align: left;
    gap: 12px;
  }

  .stat-number { font-size: 2.5rem; }

  .stat-trend { display: block; } /* hidden by default */
}

Container Queries vs Media Queries

Understanding when to use each:

ScenarioUse
Full-page layout changes (header, main, sidebar)Media query
Component behaviour based on its available spaceContainer query
A card that appears in multiple contextsContainer query
Body font size based on viewportMedia query
Typography inside a componentContainer query + cqw
Hiding navigation on small screensEither, depends on architecture

The rule of thumb: if you are styling the page shell, use media queries. If you are styling a reusable component, use container queries.

They are complementary — use both in the same stylesheet:

/* Page shell responds to viewport */
@media (min-width: 768px) {
  .layout {
    grid-template-columns: 280px 1fr;
  }
}

/* Components respond to their container */
@container (min-width: 400px) {
  .post-card {
    flex-direction: row;
  }
}

Style Queries

Container queries also support style queries — querying the computed CSS value of a custom property on the container:

/* Set a custom property on the container */
.card-wrapper {
  container-type: inline-size;
  --card-theme: dark;
}

/* Query it inside the component */
@container style(--card-theme: dark) {
  .post-card {
    background: #161c2d;
    color: #c4d4ed;
  }
}

@container style(--card-theme: light) {
  .post-card {
    background: #ffffff;
    color: #1a1a1a;
  }
}

This lets you change component themes by flipping a single custom property on the container — no class toggling, no JavaScript.

Browser support note: Style queries are supported in Chrome 111+ and Safari 17.2+. Firefox support arrived in version 129. Check caniuse.com/css-container-queries-style before using in production.

Common Gotchas

1. The container cannot query itself The container-type must be on a parent of the component you are styling. The component cannot query its own container.

/* ❌ Wrong — .card cannot query itself */
.card { container-type: inline-size; }
@container (min-width: 400px) { .card { ... } }

/* ✅ Correct — wrapper queries affect the child */
.card-wrapper { container-type: inline-size; }
@container (min-width: 400px) { .card { ... } }

2. Containment creates a new stacking context Elements with container-type other than normal create a new stacking context, similar to position: relative. This can affect z-index layering.

3. container-type: size requires explicit dimensions If you use size (not inline-size), the container needs an explicit height — otherwise content collapses. Stick to inline-size unless you specifically need height queries.

4. No em units in container query conditions Container query breakpoints use pxem and rem units in the query condition itself are not supported in all browsers yet.

/* ✅ Works everywhere */
@container (min-width: 400px) { ... }

/* ⚠ Avoid in query conditions — inconsistent support */
@container (min-width: 25em) { ... }

Browser Support

Container queries (inline-size) are Baseline — supported in:

  • Chrome 105+
  • Firefox 110+
  • Safari 16+
  • Edge 105+

This covers 93%+ of global users as of 2026. You can use container queries in production without a polyfill.

For the remaining users on older browsers, the fallback is simply the default styles you wrote before the @container block — which should be your mobile-first stacked layout.

Key Takeaways

  • Container queries respond to the parent element size, not the viewport — this makes components truly portable
  • Declare container-type: inline-size on the wrapper, not the component itself
  • Name containers with container-name when nesting to avoid ambiguity
  • Use cqw, cqi, and cqb units for fluid sizing relative to the container
  • Style queries let you switch component themes via CSS custom properties alone
  • Container queries and media queries are complementary — use media queries for page layout, container queries for components
  • Browser support is production-ready at 93%+ global coverage — no polyfill needed