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:
- Declare a containment context on the parent element using
container-type - Write a
@containerrule 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
.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.
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
--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?
Question 2
Why is container-type: inline-size preferred over container-type: size?
Question 3
What does the cqi unit represent?
Question 4
If you write @container (min-width: 400px) without specifying a container name, what does it query?
Question 5
What can style queries currently check in container queries?