Style queries for custom properties allow you to query the custom properties, also called "CSS variables", of a parent element. They are included within a <style-query> just as you would include any regular CSS property within a feature query: either with or without a value.
Standalone custom property queries
The <style-query> parameter of the style() functional notation can include just a CSS variable name; a custom property with no value. When no value is included, the query will return false if the value is the same as the value of the initial-value descriptor within the @property at-rule, if there is one. The style query will return true and match all elements that have a custom property value that differs from the initial-value or for all elements that have a custom property of any value if the custom property was declared without being registered.
Unregistered custom properties
When CSS variables are introduced via a CSS custom property value assignment, valueless custom property queries always return true.
:root {
--theme-color: rebeccapurple;
}
@container style(--theme-color) {
/* <stylesheet> */
}
In this example, the container query matches the element on which the --theme-color property was declared and all of its descendants. As the CSS variable --theme-color was declared on the :root, the style query style(--theme-color) will be true for every element within that DOM node.
Registered properties
The behavior of registered custom properties is different. When explicitly defined with the @property CSS at-rule or via JavaScript with CSS.registerProperty(), the style query style(--theme-color) only returns true for elements if the element's computed value for --theme-color is different from the initial-value set in the original definition of that custom property.
@property --theme-color {
initial-value: rebeccapurple;
inherits: true;
}
:root {
--theme-color: rebeccapurple;
}
main {
--theme-color: blue;
}
@container style(--theme-color) {
/* <stylesheet> */
}
In this example, the :root element does NOT match the style query because the value of the custom property is the same as the initial-value value. The custom property value for the element (and all the elements inheriting the value) is still rebeccapurple. Only elements that differ from the initial value, in this case, the <main> and its descendants that inherit that changed value, are a match.
Custom property with a value
If a style query includes a value for the custom property, the element's computed value for that property must be an exact match, with equivalent values only being a match if the custom property was defined with a @property at rule (or a CSS.registerProperty() method call) containing a syntax descriptor.
@container style(--accent-color: blue) {
/* <stylesheet> */
}
This container style query matches any element that has blue as the computed value of the --accent-color custom property.
In this case, other color values equivalent to sRGB blue (such as the hexadecimal code #0000ff) will match only if the --accent-color property was defined as a color with @property or CSS.registerProperty(), for example:
@property --accent-color {
syntax: "<color>";
inherits: true;
initial-value: #00f;
}
In this case, if the value of --accent-color were set to blue, #00f, #0000ff, rgb(0 0 255 / 1), or rgb(0% 0% 100%) it would return true for @container style(--accent-color: blue).
Example
In this example, we have a <fieldset> with four radio buttons. The fourth option includes a text <input> for entering a custom color.
<fieldset>
<legend>Change the value of <code>--theme</code></legend>
<ol>
<li>
<input type="radio" name="selection" value="red" id="red" />
<label for="red">--theme: red;</label>
</li>
<li>
<input type="radio" name="selection" value="green" id="green" />
<label for="green">--theme: green</label>
</li>
<li>
<input type="radio" name="selection" value="blue" id="blue" />
<label for="blue">--theme: blue</label>
</li>
<li>
<input type="radio" name="selection" value="currentcolor" id="other" />
<label for="other">Other</label>
<label for="color">color:</label>
<input text="checkbox" name="selection" value="currentcolor" id="color" />
</li>
</ol>
</fieldset>
<output>I change colors</output>
JavaScript updates the value of the CSS --theme variable on the <body> element, which is an ancestor of the <fieldset> and <output> elements, whenever a radio button is selected. When the text <input> is updated, the value of the other radio button is updated only if the other radio button is checked, which in turn updates the value of --theme.
const radios = document.querySelectorAll('input[name="selection"]');
const body = document.querySelector("body");
const other = document.getElementById("other");
const color = document.getElementById("color");
for (const radio of radios) {
radio.addEventListener("change", (e) => {
body.style.setProperty("--theme", e.target.value);
});
}
color.addEventListener("input", (e) => {
other.style.setProperty("value", e.target.value);
if (other.checked) {
body.style.setProperty("--theme", e.target.value);
}
});
We use the @property at-rule to define a CSS variable --theme to be a <color> value and set the initial-value to #00F, ensuring equivalent colors are a match regardless of what syntax is used (for example, #F00 is equal to rgb(255 0 0), #ff0000, and red).
@property --theme {
syntax: "<color>";
inherits: true;
initial-value: #f00;
}
The first style feature query is a custom property with no value. This query type returns true when the computed value for the custom property value is different from the initial-value for that property. In this case, it will be true when the value of --theme is any value other than any syntax equivalent value of #f00 ( such as red). When true, the <output> will have a 5px dotted outline. The outline color is the current value of --theme. The default text color is grey.
@container style(--theme) {
output {
outline: 5px dotted var(--theme);
color: #777;
}
}
The second and third style queries include values for the custom property. These will match if the container's --theme value is an equivalent color to the value listed, even if that value is the same as the initial-value. The first query matches elements whose --theme value is equivalent to red, blue, or green. When it is, the color will be the color current value of --theme (in the case of blue and green, overriding the grey set in the first style query).
The second style query states that when --theme is equivalent to red, the <output>'s contents will also be bold. We did this to better demonstrate that the container query is a match.
@container style(--theme: green) or style(--theme: blue) or style(--theme: red) {
output {
color: var(--theme);
}
}
@container style(--theme: red) {
output {
font-weight: bold;
}
}
Try entering different color values into the text box. You may notice that values that are sRGB equivalents of red will make the <output> red — as it matches style(--theme: red) — while removing the outline, because style(--theme) returns false if the element's value for --theme is the same as the initial value for --theme defined by the @property at-rule. Any non-red sRGB valid color value, including currentcolor or hsl(180 100% 50%), etc., makes the first style query return true; they are values that are different from the initial-value.
Because we set syntax: "<color>";, the CSS variable can only be assigned valid <color> values. Valid values for the color property that aren't value <color> values, such as unset or inherit, are invalid for this custom property, and will be ignored.
If you enter unset or gibberish, the JavaScript updates the style on the <body> to --theme: unset or --theme: gibberish. Neither of these are colors. Both are invalid and ignored. This means the initial value is inherited and unchanged, with style(--theme) returning false and style(--theme: red) returning true.
Note: When declaring custom properties, consider using @property with the syntax descriptor so the browser can properly compare computed values.