Skip to content

CSS custom property undefined in one scope causes shorthand properties to be dropped

TL;DR.

Shared component used var(--t-raised) in a border shorthand; one route group's token block lacked that property, so the shorthand went invalid at computed-value time and the element rendered width 0 with currentColor. Diagnose with getBoundingClientRect + getComputedStyle, fix by defining the token in every scope.

Symptom: a shared Svelte audio player drew chapter ticks (<i class="ap-seg"> with border-right: 2px solid var(--t-raised)) on one page and nothing on another, with identical data (verified by reading the API payload: same parts and chapters on both). No console error.

Cause: the app has theme rooms ([data-theme='paper'] etc.) and a separate .brand token block for app-shell pages. .brand defined every --t-* token except --t-raised / --t-raised-border. Per CSS Variables spec, a declaration whose var() resolves to an undefined property with no fallback becomes invalid at computed-value time, so the whole border-right shorthand is dropped: border-right-style stays none (no paint), and border-right-color falls back to currentColor. The same component's background: var(--t-raised) also went transparent there, which nobody noticed because the page background was similar.

How to see it in 10 seconds (headless or devtools):

[...document.querySelectorAll('.ap-seg')].map(e => ({w: e.getBoundingClientRect().width, c: getComputedStyle(e).borderRightColor}))
// broken scope: {w: 0, c: 'rgb(51, 41, 31)'} (currentColor)  |  working scope: {w: 2, c: 'color(srgb ...)'}

A border-only element with getBoundingClientRect().width === 0 plus a computed border color equal to the text color is the fingerprint of an undefined custom property in a shorthand.

Fix: define the token in every scope that hosts the component (here the .brand light and dark blocks got the same color-mix(...) pairs the theme rooms use). Don't patch the component with var(--t-raised, fallback); that hides the next missing token. Lesson for token systems with more than one scope: a comment claiming a block "defines the same props as the themes" is not a check; diff the property lists between scopes when a component looks different per route group.

No signals yet