惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
Engineering at Meta
Engineering at Meta
月光博客
月光博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
博客园 - Franky
The GitHub Blog
The GitHub Blog
大猫的无限游戏
大猫的无限游戏
The Cloudflare Blog
B
Blog RSS Feed
云风的 BLOG
云风的 BLOG
小众软件
小众软件
罗磊的独立博客
Microsoft Azure Blog
Microsoft Azure Blog
I
InfoQ
美团技术团队
H
Hackread – Cybersecurity News, Data Breaches, AI and More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
V2EX
C
Check Point Blog
WordPress大学
WordPress大学
博客园 - 【当耐特】
博客园 - 司徒正美
D
Docker

Jake Archibald's blog

Fixing my tooltip accessibility mistake The Goldilocks customizable select height Importing vs fetching JSON The present and potential future of progressive image rendering Fetch streams are great, but not for measuring upload/download progress Making XML human-readable without XSLT Give footnotes the boot Animating zooming using CSS: transform order is important… sometimes Firefox + custom elements + iframes bug How should <​selectedoption​> work? Video with alpha transparency on the web Garbage collection and closures
Controlling when CSS custom properties are computed
2026-08-26 · via Jake Archibald's blog

When and how CSS custom properties are calculated might not be what you expect, and it has a big impact on your styles.

The default

Take this HTML:

<div></div><div class="second-item">    <p></p></div>

And this CSS:

.second-item {    --index: sibling-index();    & > :first-child {        scale: var(--index);    }}

If you're unfamiliar with sibling-index(), here's a quick video intro I posted recently:

The question is, how much is the paragraph going to scale?

sibling-index() is 1-indexed, but is it going to pick up the index of the second div - where the custom property is defined, or its first child - where the custom property is used? The answer is: it depends.

If the code is taken in isolation, the value of the custom property will be stored as a token stream, so it's computed a little like this:

.second-item {    --index: sibling-index();    /* …Which is computed to: */    --index: sibling-index();    /* (as in, no change) */    & > :first-child {        scale: var(--index);        /* …Which is substituted for: */        scale: sibling-index();        /* …Which computes to: */        scale: 1;    }}

So the var() is substituted for sibling-index(), which is evaluated in that context. Since the paragraph is the first child of its parent, it will scale by 1.

This is reflected in getComputedStyle():

const div = document.querySelector('.second-item');console.log(getComputedStyle(div).getPropertyValue('--index'));// Logs: "sibling-index()"

But that's just the default…

Changing the default

Let's register that custom property:

@property --index {    syntax: '<number>';    inherits: true;    initial-value: 0;}

Custom properties you haven't registered behave as if they have syntax: '*', meaning 'anything', which computes to a token stream as before. But here we're using '<number>', meaning it computes the value as a number:

.second-item {    --index: sibling-index();    /* …Which is computed to: */    --index: 2;    & > :first-child {        scale: var(--index);        /* …Which is substituted for: */        scale: 2;        /* …Which computes to: */        scale: 2;    }}

Registering the custom property & giving it a syntax meant the value was computed earlier, in a different context, so the result was different.

Again, this is reflected in getComputedStyle():

const div = document.querySelector('.second-item');console.log(getComputedStyle(div).getPropertyValue('--index'));// Logs: "2"

And it's not just sibling-index() that behaves this way. This also impacts relative font units (em, ex, ch, cap, lh etc.), container query units (cqw, cqi, cqb etc.), and more.

Another thing that's impacted, although slightly differently, is URLs. In CSS, URLs are generally resolved against the stylesheet's base URL at computed-value time - so if a token-stream custom property is defined in one stylesheet and var()'d from another, the URL resolves against the consuming stylesheet. Registering the custom property with syntax: '<url>' pins it to the stylesheet that declared it.

But what about var()?

Here's a trickier example:

.second-item {    --multiplier: 1;    --index: calc(sibling-index() * var(--multiplier));    & > :first-child {        --multiplier: 3;        scale: var(--index);    }}

What happens here? Well, var() behaves differently to sibling-index(). var() is an "arbitrary substitution function", along with a bunch of other functions like if(), attr(), and ident(). These are substituted immediately, regardless of the syntax of the custom property. So:

.second-item {    --multiplier: 1;    --index: calc(sibling-index() * var(--multiplier));    /* …Which is substituted for: */    --index: calc(sibling-index() * 1);}

What happens next depends on the defined syntax of --index. It'll either stay as the above token stream, or it'll compute down to 2 - the same rules as before. In both cases, --multiplier: 3 on the child is ignored.

This was a detail of CSS that I sort-of half-knew, as in, I knew @property changed computation timing somehow, but I hadn't fully understood when or how, until I looked it up today. I hope you understand it now too!

View this page on GitHub