CSS Style Queries: Reacting to a Custom Property, Not a Size
Style queries let a component react to a custom property set by an ancestor. What ships today, and when to reach for one.
Table of contents
Size queries ask how much room a component has. Style queries ask what kind of place it is in.
@container style(--density: compact) {
.card {
padding: 0.5rem;
}
}The card now responds to a custom property set anywhere above it, without a variant class threaded through every layer and without a single line of JavaScript.
Key takeaways#
- Style queries match on custom properties only.
style(display: flex)is not supported in any shipping browser. - They need no
container-type. The defaultnormalis already a style container, so there is no containment and no collapse risk. - Every element is a style container by default, which means an unnamed style query resolves against the direct parent.
- The value comparison is on the computed value, so
--density: compactand--density: compactmatch, but1remand16pxdo not. - The natural use is contextual variants — density, tone, emphasis — that were previously modifier classes.
The problem they solve#
A component that must look different in different contexts has had two options, and both leak.
A modifier class means every consumer has to remember to pass it. <Card className="card--compact"> inside a sidebar, everywhere a sidebar appears. Miss one and it silently renders wrong.
A descendant selector inverts the dependency badly. .sidebar .card { padding: 0.5rem } puts knowledge of the card inside the sidebar's stylesheet, and now the card's appearance is defined in two files.
A style query lets the context declare what kind of context it is, and the component decide what to do about it:
/* The context states a fact about itself. */
.sidebar {
--density: compact;
}
/* The component decides what that means. */
@container style(--density: compact) {
.card {
padding: 0.5rem;
font-size: 0.875rem;
}
}The sidebar knows nothing about cards. The card knows nothing about sidebars. Adding a third dense context is one custom property, not an edit to the card.
No container-type required#
This is the part that trips people who come from size queries. Style queries need no container-type declaration, because every element is a style query container by default.
That is a real advantage, not a technicality. container-type: inline-size brings layout and style containment with it — a new stacking context, a new containing block for fixed-position descendants, no margin collapsing through the boundary. Style queries bring none of that. There is nothing to collapse and nothing to reposition.
Because every element is a container, name yours#
The convenience has a sharp edge. Since every element is a style container, an unnamed @container style(...) query resolves against the direct parent — not the nearest element that set the property.
<div class="sidebar">
<!-- --density: compact -->
<div class="wrapper">
<!-- sets nothing -->
<div class="card">…</div>
</div>
</div>An unnamed query from .card looks at .wrapper. Custom properties inherit, so .wrapper does have --density: compact and this happens to work — but it works by inheritance, not by intent, and it breaks the moment anything in between resets the property.
Naming makes the target explicit:
.sidebar {
container: layout-context / normal;
--density: compact;
}
@container layout-context style(--density: compact) {
.card {
padding: 0.5rem;
}
}Matching rules that surprise people#
The comparison is on computed values, as strings. --density: compact matches style(--density: compact). It does not match style(--density: Compact).
Unregistered custom properties are not parsed. Without @property, --space: 1rem is just the token sequence 1rem, so style(--space: 16px) does not match even though the two are equivalent lengths. Registering the property gives it a type and a computed value:
@property --space {
syntax: '<length>';
inherits: true;
initial-value: 0px;
}Testing for presence works too. @container style(--compact) matches when the property has any value other than its initial one — useful for a boolean flag where the value itself carries no information.
Combining with size queries#
One container can answer both kinds of question, and the conditions can be combined:
.card-wrapper {
container: card / inline-size;
}
@container card (width > 30rem) and style(--density: comfortable) {
.card {
grid-template-columns: 12rem 1fr;
gap: 1.5rem;
}
}The horizontal layout applies only when there is room and the context wants a comfortable density. The container shorthand set both the name and the size type, and style querying came along free.
Where style queries earn their place#
- Density. Compact in a sidebar or a data table, comfortable in a main column.
- Tone. A
--tone: dangerregion tinting every child that opts in, without adangerclass on each. - Emphasis. A featured area promoting the cards inside it.
- Theming that is not a media query. A section that is dark regardless of the user's colour scheme, where a
prefers-color-schemerule would be wrong.
The common thread: a fact about the context that several unrelated descendants each want to interpret differently. That is precisely the case a modifier class handles badly, because the class has to be applied to each descendant by whoever assembles the markup.
Support, as of August 2026#
Style queries for custom properties are supported in current Chrome, Edge, Safari and Firefox.
Style queries for plain CSS properties — style(display: flex) — are in the specification but not shipping anywhere. Do not write them; a query the browser cannot evaluate never matches, so the styles inside simply never apply and nothing tells you why.
Degradation is clean either way. An unsupported @container block is skipped, so whatever the component declares outside the block is what renders. Write the default styles as the base and let the query add to them:
.card {
padding: 1rem; /* the fallback, always applied */
}
@container layout-context style(--density: compact) {
.card {
padding: 0.5rem;
}
}For the containment rules that size queries need and style queries do not, see container-type: inline-size vs size.
Style queries nest deeply once you have more than one. The CSS Formatter keeps the nesting readable while you work through it.
Tags
- CSS
- Design Systems
- Layout
- Responsive