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 simple 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;
inherited: 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 (let i = 0; i < radios.length; i++) {
radios[i].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.