The problem with media queries
A media query asks about the viewport. But a card does not care how wide the window is — it cares how wide it is. The same card might be 900px wide in a hero, 400px in a two-column grid and 240px in a sidebar, all on the same screen.
With media queries you end up writing .sidebar .card and .hero .card overrides, and the component stops being portable: it only works in the places you thought of.
Declaring a container
inline-size— queries about width. This is what you want almost always.size— width and height, and it requires the container to have a definite size in both directions.normal— no size queries; only style queries work.
.card-wrap {
container-type: inline-size;
container-name: card;
}
/* shorthand for both */
.card-wrap {
container: card / inline-size;
}
The rules inside @container apply to the descendants of the container, never to the container element itself. So you need a wrapper: the element you query and the element you style cannot be the same one.
Querying it
Drop that card into a sidebar and it stacks. Drop the same markup into a wide hero and it goes two-column. Nothing outside the component had to be told.
.card {
display: grid;
gap: 0.75rem;
}
@container card (min-width: 28rem) {
.card {
grid-template-columns: 10rem 1fr;
align-items: center;
}
}
@container card (min-width: 44rem) {
.card {
grid-template-columns: 16rem 1fr;
}
.card h3 { font-size: 1.6rem; }
}
Without a name
Leaving the name out queries the nearest ancestor that is a container. That is fine for a simple case; name them as soon as there is more than one level, or you will be debugging which container answered.
.wrap { container-type: inline-size; }
@container (min-width: 30rem) {
.card { grid-template-columns: 1fr 1fr; }
}
Container query units
This is the piece media queries could never do: a heading that is large in a wide card and small in a narrow one, on the same screen, with one rule.
.card h3 {
font-size: clamp(1.1rem, 0.9rem + 2cqi, 2rem);
}
| Unit | Relative to |
|---|---|
cqw | 1% of the container's width |
cqh | 1% of its height |
cqi | 1% of its inline size — usually the width |
cqb | 1% of its block size |
cqmin / cqmax | The smaller / larger of cqi and cqb |
Style queries
A style query asks about the value of a custom property on the container. It lets a variant set one token and have descendants respond, without every descendant needing a modifier class. Support is newer than size queries — check before relying on it.
.panel { container-name: panel; }
@container panel style(--tone: warning) {
.panel-title { color: #a8471f; }
}
What it costs
container-type: inline-sizeappliescontain: layout inline-size style— the element becomes a containment boundary. Its children can no longer affect its width.- That means a container cannot be sized by its contents in the inline direction. A
container-type: sizeelement needs a definite height too, or it collapses. - Do not put
container-typeonbodyor on every div. Put it on the specific wrapper whose width the component should follow.
Using both
Media queries and container queries answer different questions, and a real stylesheet uses both:
- Media queries for the page: how many columns does the layout have, is the navigation a bar or a drawer, is this print, does the reader want reduced motion.
- Container queries for components: how does this card, this media object, this table arrange itself in the space it was given.