You can now write @container style(--status: warn) and have CSS react to the value of a custom property, in every major browser. Container style queries became Baseline on May 19, 2026, when Firefox 151 shipped them, joining Chrome and Edge 111 and Safari 18.
Size container queries got all the attention. Style queries are the quieter half of the same feature, and I think they're the more surprising one: instead of asking "how wide is my container?", a rule asks "what value does my container have for this variable?". That turns a custom property into a switch that CSS can read.
What problem it solves
Think of a status card with three states: operational, degraded, down. Each state changes the border, the glow, the icon, the badge text and the colour of a chart. The usual answer is a modifier class or a data-status attribute, plus one selector per state for every piece of the card.
With a style query, the state lives in one custom property, and every descendant can ask about it:
<div class="card" style="--status: warn">
<span class="badge">Degraded</span>
</div>
.badge { background: rgb(0, 212, 170); }
@container style(--status: warn) {
.badge { background: rgb(251, 191, 36); }
}
That's the whole trick in the demo above. Each card carries style="--status: ok" and the only thing the JavaScript does on click is card.style.setProperty('--status', next). The colour, the ▲/✕ icon, the badge text (via content: var(--badge)), the latency figure, the progress bar and the pulsing ring are all CSS reacting to that value. The board view uses a second variable, --view: list, on the board element, and even the highlighted line in the code panel is a style query reading --last.
What makes this different from an attribute selector is that custom properties inherit and cascade. The value can come from a parent, a theme class higher up, a media query or an inline style, and every element below it can query it without knowing where it was set.
How it works
A few rules that I checked against MDN and the spec, and then in the browser:
- No
container-typeneeded. Every element is a style container. You only needcontainer-typefor size queries. - An element queries its ancestors, not itself. The query is evaluated against an ancestor container, so in the example above
.badgecan react to--status, but a rule that tries to style.carditself from its own--statuswon't match. That's why every card in the demo has an inner.bodythat carries the visual styles. - Stick to custom properties. The spec also allows normal properties, as in
style(color: red), but MDN's guide says custom properties are the only style feature currently supported, and that query didn't match in the Chromium I tested with. - A query without a value checks "is it set?".
style(--flag)is true when the computed value differs from the initial value. For an unregistered custom property, that means any value at all.
The gotcha: plain matching compares tokens
style(--status: warn) compares the computed value of the property with the value you wrote. For an unregistered custom property, that computed value is basically the text you wrote, so --accent: #00f does not match style(--accent: blue), and --n: calc(6/2) does not match style(--n: 3). I tested both in Chromium and neither matches.
If you need equivalent values to match, register the property with a type:
@property --accent {
syntax: "<color>";
inherits: true;
initial-value: black;
}
/* Now --accent: #00f matches too */
@container style(--accent: blue) {
.icon { color: var(--accent); }
}
For keyword-like states such as ok, warn and down, plain matching is exactly what you want, and you don't need @property at all.
Browser support and other gotchas
- Support: Chrome and Edge 111, Safari 18, Firefox 151. Baseline "newly available" since May 19, 2026, so check your audience before you drop a fallback.
- Safari and
:root: MDN's compatibility data notes that in Safari the document element can't be a container. If you set a variable on:rootand query it, the children of<body>are fine, because<body>inherits the value and acts as the container. A rule that styles<body>itself from it won't match. - Feature detection:
@supportscan't test an at-rule like this. In the demo I use a tiny probe: a hidden element sets--sq: yes, a style query gives its child a 7 px width, and JavaScript checks the computed width. If it doesn't match, the page adds a class and falls back todata-statusattribute selectors that set the same variables. - Range syntax isn't Baseline. MDN documents a newer form with comparison operators, such as
style(--columns >= 3), which compares values numerically. MDN's compatibility data lists Chrome 142 and no Safari support yet, so I left it out of the demo.
What to keep an eye on
- Range syntax across engines. Numeric comparisons like
style(0 < --n < 10)are where style queries get really expressive. Once Safari ships them, a lot of "set a class from JavaScript when the number crosses a threshold" code can go. - Querying normal properties.
style(display: flex)orstyle(font-weight: bold)are in the spec, but you can't count on them in every browser yet. When they arrive, components will be able to adapt to how their parent is styled, not only to variables you set on purpose. - Design systems. A single
--variantor--tonevariable on a component, with style queries inside it, is a clean alternative to stacks of modifier classes. I expect to see that pattern a lot more now that it works everywhere.