
Container queries
Style an element by the size of its container, not the viewport
Your browser does not support container-type, so the samples below fall back to their default layout.
Size queries
The card below switches from stacked to side-by-side at a container width of 28rem. Drag the resize handle in the bottom-right corner of the dashed box — the viewport never changes (see for more information).
A card that adapts
Same markup, same viewport — only the container width differs.
.cq-resizable {
container-type: inline-size;
container-name: card;
}
.cq-card {
display: grid;
gap: 1rem;
}
@container card (min-width: 28rem) {
.cq-card {
grid-template-columns: 14rem 1fr;
align-items: center;
}
}Container query units
cqi is 1% of the container’s inline size — the container-scoped answer to vw. Resize the box and the headline scales with it.
Fluid type with 8cqi
.cq-fluid {
container-type: inline-size;
}
.cq-fluid__headline {
/* 8% of the container's inline size, not the viewport */
font-size: clamp(1rem, 8cqi, 3rem);
}Style queries
A container query can also match a custom property value. Both badges below share one class — only the --tone value on their container differs. Style queries need no container-type, but browser support is narrower than for size queries.
.cq-tone { --tone: info; }
.cq-tone[data-tone="warn"] { --tone: warn; }
.cq-badge {
background: #dbeafe;
color: #1e40af;
}
@container style(--tone: warn) {
.cq-badge {
background: #fef3c7;
color: #92400e;
}
}Style queries with typed attr()
The grid below declares its whole configuration as data-* attributes. Typed attr() feeds two of them straight into grid-template-columns and gap, and turns the other two into custom properties the cards pick up with style queries. The controls only change attributes (adapted from utilitybend).
Your browser does not support typed attr(), so the grid keeps its default column size and gap. The accent and the hero card still follow the controls, through attribute selectors instead of attr().


Canteen by the windows

Neon lounge

Seating and floor tiles

Daylight through the arches
<div class="fluid-grid"
data-min-size="220px"
data-gap="2"
data-featured="true"
data-accent="terracotta">
<article class="card">…</article>
</div>.fluid-grid {
--featured: attr(data-featured type(<custom-ident>), false);
--accent: attr(data-accent type(<custom-ident>), terracotta);
display: grid;
grid-template-columns: repeat(auto-fit,
minmax(min(attr(data-min-size type(<length>), 220px), 100%), 1fr));
gap: attr(data-gap cqi, 2cqi);
}
@container style(--featured: true) {
.card:first-child {
grid-column: span 2;
grid-row: span 2;
}
}
@container style(--accent: sage) {
.card { --accent-color: oklch(48% 0.11 150); }
}
.card {
--card-accent: var(--accent-color, oklch(52% 0.16 35));
border-top: 3px solid var(--card-accent);
}Gotchas
- A container cannot query itself — the rules inside
@containermust target a descendant. container-type: inline-sizeapplies size containment, so the container no longer takes its height from its children in the inline axis.- Without
container-name, a query matches the nearest ancestor container — naming makes the target explicit.