Most developers met container queries as a way to ask how wide a card’s parent is. That is only half of the specification. The other half, style queries, asks a different question: what is the computed value of a custom property on an ancestor? The answer drives a conditional block of CSS, which turns custom properties into a small state API that components can read without a class name, a data attribute, or a script.
The feature has been in Chromium since version 111 and in Safari since version 18. Firefox 151 closed the gap, and web.dev’s May 2026 platform roundup lists style queries for custom properties as Baseline Newly available. That status matters, because the feature moved from “nice demo” to something a production stylesheet can rely on, with the usual caveat that Newly available means the newest engine has only just shipped it.
The Basic Syntax
A style query uses the @container at-rule with a style() function. Unlike size queries, the container does not need a container-type declaration. Every element is already a style container for the purpose of this query, and the browser looks up the tree for the nearest ancestor that matches.
.card-wrapper {
--tone: calm;
}
.card-wrapper.urgent {
--tone: alert;
}
@container style(--tone: alert) {
.card {
border-color: crimson;
background: #fff5f5;
}
}
The query selects descendants of the element that holds --tone. It does not style that element itself, which is a deliberate rule to prevent a container from changing the very value it is being tested on. If the wrapper and the card are the same element, the query never fires on it, so split the roles across a parent and child.
You can also ask whether a property is set at all with style(--tone), and combine tests with and, or, and not. MDN documents all three operators for style queries.
Plain Syntax Versus Range Syntax
There are two comparison styles, and they behave differently.
Plain syntax uses a colon, as in style(--columns: 3). For an unregistered custom property, the browser compares the value as written. 3 matches 3, but 3 does not match calc(1 + 2), because the tokens differ.
Range syntax uses comparison operators, as in style(--columns >= 3). Here the browser parses both sides and compares them as values, so numeric comparisons work and calc() is resolved first. MDN describes this form in its container query documentation. Treat it as a separate support question from the baseline for plain custom property queries, and check a compatibility table before depending on it in a shipping stylesheet.
Registered properties change the rules
If you declare the property with @property and a syntax, equivalence is evaluated on computed values. MDN’s example shows blue, #0000ff, and rgb(0 0 255) all matching style(--accent: blue) for a registered color property. A bare style(--accent) on a registered property is true only when the computed value differs from the initial value. For unregistered properties, a bare query is true whenever the property is defined.
@property --density {
syntax: "<custom-ident>";
inherits: true;
initial-value: comfortable;
}
Registering also gives you type checking and a defined fallback, which makes a component’s public “inputs” easier to document.
What Style Queries Cannot Do Yet
The most common misconception is that @container style(font-weight: bold) works. It does not. Per MDN, only custom properties are supported as style features today, and queries against ordinary properties such as max-width or font-weight are a future capability. If a tutorial shows one, it is describing the direction of the specification, not shipping behavior.
Two other limits are worth knowing:
- The left side of a plain query must be a bare property name.
style(var(--n): 3)is invalid. - The CSS-wide keywords
revertandrevert-layermake the query evaluate to false.
Because custom properties inherit by default, a value set high in the tree reaches every descendant. That is usually what you want, but it also means a nested component can inherit a state meant for a different one. Resetting the property at the boundary of each component, or registering it with inherits: false, keeps states from leaking.
Where They Earn Their Place
Style queries fit best where a component has a small number of named variants that a parent chooses.
Theme and density variants
A layout region sets --density: compact, and every card, table row, and list inside adjusts padding and type size without each one needing a modifier class. The author of the region changes one value, and the components follow.
Design system tokens with meaning
Instead of passing a class to every child, a system can expose a documented property such as --emphasis: high. Components read it and decide how to respond. The contract is a single property name, which is easier to maintain than a family of utility classes.
Combining with size queries
Style and size queries can be used together, so a card can switch layouts when its container is wide and switch tone when the container declares an alert state. Keeping them in separate @container blocks makes the intent easier to read.
When a Class Is Still Better
Style queries are not a replacement for every modifier. If the variant also needs to change markup, set an ARIA attribute, or be targeted by JavaScript, a class or data attribute remains the clearer choice. Attribute selectors also work in every browser that predates this feature, which matters if your support policy reaches back past Safari 18 or recent Firefox releases.
A sensible approach is progressive enhancement. Write the default presentation first, add the style query block as a refinement, and let browsers without support fall back to the baseline look. Since Firefox 151 is the newest engine to ship the feature, expect some visitors on older Firefox builds for a while.
Practical Takeaway
Pick one repeated variant in your own codebase, such as a theme, a density setting, or a tone, and move it from a modifier class to a documented custom property read through @container style(). Keep the property on a parent that is not the element being styled, register it with @property if you want value equivalence and a default, and leave range comparisons until you have confirmed support for your audience. The result is a component whose inputs are visible in one place and whose variants need no extra markup.



