Style queries
CSS consumes the properties prop-for-that writes in two ways:
var()interpolates on a value. Best for continuous numbers (--live-pointer-x-ratio,--live-value-pct) fed throughcalc().@container style()applies a conditional rule when a property equals a value. Best for discrete, boolean, or enum state (--live-online,--live-visible,--const-has-entered,--live-value: 100,--live-paused,--live-battery-low, capability tiers).
Style queries are what make the discrete signals shine: a value flips whole rule blocks on and off, which var() can’t do.
The container rule
Section titled “The container rule”A style query matches against an element’s nearest ancestor container, and for style queries every element is a container by default (no container-type needed). So:
- Global sources write to
:root, an ancestor of everything, so they’re queryable anywhere. - Element sources write to the bound element, so its descendants query it. (An element can’t style-query its own property, only an ancestor’s.) That’s exactly why a gauge inside the container-bound
rangewrapper can react.
Example 1: a global boolean (offline)
Section titled “Example 1: a global boolean (offline)”import { register, propsFor } from 'prop-for-that'import { online } from 'prop-for-that/plugins'
register(online)propsFor(['online']) // writes --live-online (1/0) to :root.offline-banner { display: none; }
/* :root is an ancestor container, so this works anywhere on the page */@container style(--live-online: 0) { .offline-banner { display: block; } body { filter: grayscale(1); }}Example 2: an element milestone (slider at max)
Section titled “Example 2: an element milestone (slider at max)”<script type="module">import 'prop-for-that/auto'</script>
<!-- auto binds it from the attribute; descendants of #meter inherit --live-value --><div id="meter" data-props-for="range">…</div>/* .gauge is inside #meter and inherits --live-value, so its descendants can match the exact integer, which var()/calc() can't express */@container style(--live-value: 100) { .gauge__num { color: var(--accent); } .gauge__flag::after { content: 'max'; }}Example 3: a capability tier (proposed capability plugin)
Section titled “Example 3: a capability tier (proposed capability plugin)”/* ship lighter media on lite connections */@container style(--live-delivery-mode: 1) { .hero-video { display: none; } .hero-poster { display: block; }}Equality today, ranges later
Section titled “Equality today, ranges later”Style queries match on equality (style(--live-net-type: 4)), not comparisons. Range queries (style(--live-battery-level < 0.2)) aren’t stable across browsers yet.
That suits this library: emit discrete booleans and tiers from JS, and you get threshold styling that works today. The battery plugin’s --live-battery-low (1/0), or a capability tier, lets you write:
@container style(--live-battery-low: 1) { .autoplay { animation-play-state: paused; }}No waiting for range queries. Pair continuous values with var(), discrete values with @container style().
Browser support
Section titled “Browser support”Style queries for custom properties ship in Chromium 111+, Safari 18+, and Firefox 128+. Where unsupported, @container style() blocks are ignored, so treat them as progressive enhancement and keep a sensible default outside the query (as the examples above do).