← Back to Index

Lesson 10

Container Queries

Media queries respond to the viewport. That works for page-level layouts, but components live inside other components. A card in a sidebar needs different styles than the same card in a wide main column - and the viewport hasn't changed. Container queries let components respond to the size of their parent container instead of the viewport.

The problem container queries solve

Imagine a card component used in three contexts: a narrow sidebar (250px), a medium widget area (400px), and a full-width hero section (800px). With media queries, you'd need to know where the card is used and write breakpoints for each context. If the layout changes, those breakpoints break.

Container queries flip the model: the card itself decides how to display based on how much space its parent gives it. Move the card anywhere - it adapts automatically.

The mental model shift: Media queries ask "how wide is the screen?" Container queries ask "how wide is the box I'm inside?" This makes components truly portable - they work regardless of page layout.

Two steps: declare a container, then query it

Container queries require two things:

  1. Declare a containment context on the parent element using container-type
  2. Write a @container rule that queries that container's size
/* Step 1: Make the parent a container */
.card-wrapper {
  container-type: inline-size;
}

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

Try resizing the container below by dragging its right edge:

Resize this container - the card layout changes at 350px and 500px

↔ Drag right edge to resize

Container-aware card
This card adapts to its container width, not the viewport. Narrow = stacked. Wide = horizontal.
.card-wrapper {
  container-type: inline-size;
  container-name: card-container;
}

.card {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* At 350px+, switch to horizontal layout */
@container card-container (min-width: 350px) {
  .card {
    flex-direction: row;
    align-items: center;
  }
  .card .image {
    width: 80px;
    flex-shrink: 0;
  }
}

/* At 500px+, increase spacing and font size */
@container card-container (min-width: 500px) {
  .card {
    padding: 1.5rem;
    gap: 1rem;
  }
  .card .title {
    font-size: 1.1rem;
  }
}

container-type - what can be queried

The container-type property tells the browser what dimensions to track for container queries:

Value Tracks Use case
inline-size Width only (inline axis) Most common - responsive components based on available width
size Both width and height Rare - when you need to query height too
normal Neither (default) Element is not a size query container (can still be a style query container)
/* Track only width (most common) */
.wrapper {
  container-type: inline-size;
}

/* Track both width and height */
.wrapper {
  container-type: size;
}

/* Not a size container (default) */
.wrapper {
  container-type: normal;
}

Why inline-size is preferred over size: Tracking height creates circular dependencies. If content height depends on container height, and container height depends on content height, the browser enters an infinite loop. inline-size avoids this because width in block layout flows top-down - the container's width is determined independently of its children's height.

container-name - targeting specific containers

When you have nested containers, container-name lets you query a specific one:

/* Name your containers */
.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

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

/* Query a specific container by name */
@container sidebar (min-width: 300px) {
  .nav-item { display: flex; gap: 0.5rem; }
}

@container main (min-width: 600px) {
  .article { columns: 2; }
}

/* Shorthand: container property combines both */
.sidebar {
  container: sidebar / inline-size;
  /* Equivalent to:
     container-name: sidebar;
     container-type: inline-size; */
}

Without a name, @container (min-width: 400px) queries the nearest ancestor that has container-type set. Naming is optional for simple cases but essential when you have multiple containers in a component hierarchy and need to target a specific one.

Container query units

Container queries introduce new units that are relative to the container's dimensions rather than the viewport:

Unit Meaning Viewport equivalent
cqi 1% of container's inline size (width) vi / vw
cqb 1% of container's block size (height) vb / vh
cqw 1% of container's width vw
cqh 1% of container's height vh
cqmin Smaller of cqi and cqb vmin
cqmax Larger of cqi and cqb vmax

Text sized with cqi - resize the container to see the font scale

↔ Drag right edge to resize

This text uses font-size: clamp(0.8rem, 3cqi, 1.5rem) - it scales with the container width, not the viewport.

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

/* Font size relative to container width */
.responsive-text {
  font-size: clamp(0.8rem, 3cqi, 1.5rem);
}

/* Padding relative to container */
.card {
  padding: 2cqi;
}

/* These units work anywhere you'd use a length  -  
   margins, gaps, widths, border-radius, etc. */

cqi is to containers what vw is to the viewport. Use it for fluid typography, spacing, or sizing that should scale with the component's available width rather than the screen width. Combine with clamp() to set minimum and maximum bounds.

Ad

Style queries - querying custom property values

Container queries aren't limited to size. Style queries let you query the computed value of custom properties on a container:

Click the button to toggle the container's --theme property

This card's styles change based on the container's --theme custom property value, not a class on the card itself.
/* Container sets a custom property */
.theme-wrapper {
  container-type: normal;  /* style queries don't need size containment */
  --theme: light;
}

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

/* Query the custom property value */
@container style(--theme: dark) {
  .card {
    background: #1a1a2e;
    color: #f0f0f0;
  }
}

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

Style queries don't require container-type: inline-size. Any element can be a style query container - you only need size containment for size queries. This means style queries work on elements that would be problematic to size-contain (like inline elements or flex items).

What about querying standard properties?

Currently, style queries only work with custom properties. You cannot query computed values of standard properties like display, color, or font-size. This is a deliberate limitation - querying arbitrary computed styles would create performance issues and circular dependencies.

The workaround pattern is to use custom properties as a signalling mechanism:

/* Instead of trying to query "is this container flex?" (not possible)
   set a custom property that signals the state: */

.container.is-flex {
  display: flex;
  --layout: flex;
}

.container.is-grid {
  display: grid;
  --layout: grid;
}

/* Now you can query the signal */
@container style(--layout: flex) {
  .child { margin-inline-start: auto; }
}

@container style(--layout: grid) {
  .child { grid-column: span 2; }
}

Media queries vs Container queries

Aspect Media Queries Container Queries
Responds to Viewport dimensions Parent container dimensions
Best for Page-level layout shifts Component-level adaptation
Reusability Breakpoints tied to specific layouts Components adapt anywhere they're placed
Setup required None - viewport is always available Parent must declare container-type
Units vw, vh, vmin, vmax cqi, cqb, cqw, cqh, cqmin, cqmax
Use together? Yes - media queries for page layout, container queries for components within that layout

They're complementary, not competing. Use media queries to rearrange the page structure (e.g., sidebar collapses on mobile). Use container queries to let individual components adapt to whatever space they're given. The combination creates truly modular, responsive design systems.

Browser support

Container size queries (@container with size conditions) are supported in all modern browsers since early 2023: Chrome 105+, Firefox 110+, Safari 16+. Style queries for custom properties landed in Chrome 111+ and Safari 18+, with Firefox support in development.

Container query units (cqi, cqb, etc.) have the same support as size queries - all modern browsers handle them.

/* Progressive enhancement pattern */
.card {
  /* Base styles  -  works everywhere */
  display: flex;
  flex-direction: column;
}

/* Enhancement for browsers that support container queries */
@supports (container-type: inline-size) {
  .card-wrapper {
    container-type: inline-size;
  }

  @container (min-width: 400px) {
    .card {
      flex-direction: row;
    }
  }
}

Retrieval check

Question 1

What property makes an element a query container for size-based container queries?

container-name
container-type: inline-size
display: container
contain: layout

Question 2

Why is container-type: inline-size preferred over container-type: size?

inline-size has better browser support
size doesn't support named containers
Tracking height creates circular dependencies in layout calculations
size is deprecated and will be removed

Question 3

What does the cqi unit represent?

1% of the viewport's inline size
1% of the container's block size
1% of the container's inline size (width)
1 pixel relative to the container

Question 4

If you write @container (min-width: 400px) without specifying a container name, what does it query?

The document root element
The viewport width (like a media query)
The nearest ancestor with container-type set
It throws a CSS error and is ignored

Question 5

What can style queries currently check in container queries?

Any computed CSS property on the container
The container's display type and position
Custom property values on the container
The container's class list and attributes