CSS Container Queries

Container queries let a component adapt its layout based on the size of its parent container — not the browser window. This is the biggest shift in responsive design since media queries. A card that looks different at 300px wide and 600px wide, regardless of what screen the user is on, is the core use case.

The Problem with Media Queries

DIAGRAM — Why media queries fall short for components:

A card component used in two places:

Sidebar (300px wide):          Main content (700px wide):
┌──────────────┐               ┌──────────────────────────────┐
│Card (stacked)│               │ Card (horizontal layout)     │
└──────────────┘               └──────────────────────────────┘

@media (min-width: 768px) doesn't help here.
Both cards are on the same 1200px-wide screen.
The screen width is the same — but the card's container is different.

Media queries watch the screen. Container queries watch the container.

How Container Queries Work

Two steps are required:

  1. Declare the parent as a containment context using container-type
  2. Write a @container rule that fires when the container reaches a certain size

Step 1 — Declare a Containment Context

/* The parent that the component lives inside */
.card-wrapper {
  container-type: inline-size;
  /* "inline-size" means: track the horizontal (width) size */
}

container-type values:

  • inline-size — tracks width (most common, works for most layouts)
  • size — tracks both width and height
  • normal — no size tracking (default), but can still be queried for style

Step 2 — Write a @container Rule

/* Base card style (small — stacked layout) */
.card {
  display: flex;
  flex-direction: column;
}

/* When the container reaches 500px or more, go horizontal */
@container (min-width: 500px) {
  .card {
    flex-direction: row;
  }
}
DIAGRAM — @container response:

.card-wrapper at 300px wide:
┌──────────────────┐
│  [Image]         │  ← stacked: flex-direction: column
│  [Title]         │
│  [Description]   │
│  [Button]        │
└──────────────────┘

.card-wrapper at 600px wide:
┌───────────────────────────────────────┐
│ [Image] │ [Title]                     │  ← horizontal: row
│         │ [Description]               │
│         │ [Button]                    │
└───────────────────────────────────────┘

Named Containers

When you have multiple nested containers, name them so @container rules target the right one.

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

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

/* Only fires when the sidebar container is narrow */
@container sidebar (max-width: 250px) {
  .nav-item span {
    display: none; /* hide label, show icon only */
  }
}

/* Only fires when the main content container is wide */
@container main (min-width: 700px) {
  .article-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
/* Shorthand for container-type + container-name */
.sidebar {
  container: sidebar / inline-size;
}

Container Query Units

Inside a @container block, you can use special units relative to the container size.

UnitMeaning
cqw1% of the container's width
cqh1% of the container's height
cqi1% of the container's inline size (usually width)
cqb1% of the container's block size (usually height)
cqmin1% of the smaller of cqi or cqb
cqmax1% of the larger of cqi or cqb
/* Font size scales with the container, not the viewport */
@container (min-width: 300px) {
  .card-title {
    font-size: 4cqw; /* 4% of the container width */
  }
}

Practical Full Example — Reusable Card

<!-- Used in a narrow sidebar -->
<aside class="sidebar">
  <div class="card">...</div>
</aside>

<!-- Used in a wide main area -->
<main class="content-area">
  <div class="card">...</div>
</main>

/* Both containers declared */
.sidebar, .content-area {
  container-type: inline-size;
}

/* Card base — mobile-like stacked layout */
.card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  background: white;
  border-radius: 8px;
}

.card img {
  width: 100%;
  height: 180px;
  object-fit: cover;
  border-radius: 6px;
}

.card h2 { font-size: 1rem; }

/* Card responds to its container — not the screen */
@container (min-width: 480px) {
  .card {
    flex-direction: row;
    align-items: center;
  }

  .card img {
    width: 200px;
    height: 140px;
    flex-shrink: 0;
  }

  .card h2 { font-size: 1.25rem; }
}

@container (min-width: 700px) {
  .card {
    padding: 24px;
    gap: 20px;
  }

  .card h2 { font-size: 1.5rem; }
}
DIAGRAM — Same card, different containers on the same screen:

Sidebar (300px):        Main content (800px):
┌──────────────┐        ┌────────────────────────────────────┐
│ [Card image] │        │ [Image] │ Card Title (bigger text) │
│ Card title   │        │         │ Description              │
│ Description  │        │         │ [Button]                 │
│ [Button]     │        └────────────────────────────────────┘
└──────────────┘

One component. Two containers. Two different layouts.
Zero JavaScript.

Container Queries vs Media Queries

FeatureMedia QueriesContainer Queries
WatchesViewport (browser window)Parent container size
Best forPage-level layout breakpointsComponent-level adaptation
Reusable componentsLimited — breaks when movedExcellent — adapts anywhere
Browser supportAll browsersAll modern browsers (2023+)

When to Use Container Queries vs Media Queries

DIAGRAM — Decision guide:

"Is this about the whole page layout?"
  YES → Use @media
    Example: switch from sidebar layout to mobile stack at 768px

"Is this about a reusable component?"
  YES → Use @container
    Example: card switches from stacked to side-by-side based on its parent

Container queries are the future of component-based CSS. They let you build truly reusable components that adapt to any context they're placed in — no JavaScript, no wrapper-specific overrides, just clean CSS that responds to its environment.

Leave a Comment

Your email address will not be published. Required fields are marked *