Nothing catches a reference to a CSS custom property that no longer exists #204

Closed
opened 2026-07-26 19:56:29 +00:00 by robbertbos · 1 comment
Owner

#196 retired Waggle's own token set (--space-*, --text-*, --radius-*, --font-weight-*, --shadow-*, --transition-*, --line-height-*, --focus-ring) in favour of NLDD's. It missed two consumers of --radius-full, and that shipped to main unnoticed until a grep found it days later (#202).

Why it slipped through every gate

A retired custom property fails silently by construction. var(--gone) with no fallback makes the whole declaration invalid, so the browser drops it - no error, no warning, no console output. vue-tsc does not look inside CSS variable names, ESLint does not parse <style module> values, and no test asserts on computed styles. The only signal is visual, and the two cases here were a border-radius on a small badge, which is exactly the kind of thing an eye skips.

The same class of bug hit twice in that PR: the focus ring in #198 was a token that did exist but meant something else (--semantics-focus-ring-box-shadow is the gap, not the ring), which resolves to a valid value and is even harder to spot.

Options

  1. A stylelint rule. declaration-property-value-no-unknown does not cover this; a small custom rule or stylelint-value-no-unknown-custom-properties could check every var(--x) against the declared set (ours plus NLDD's settings.css).
  2. A CI grep against a list of retired names. Cheap, catches renames, misses typos in new names.
  3. Require a fallback on every var() in our own CSS, so a missing token degrades instead of dropping the declaration.

Option 1 is the real fix; option 2 is a ten-line guard that would have caught this one. NLDD's own skill flags renamed tokens as "de meest gemiste val", so this is worth a guard rather than vigilance.

#196 retired Waggle's own token set (`--space-*`, `--text-*`, `--radius-*`, `--font-weight-*`, `--shadow-*`, `--transition-*`, `--line-height-*`, `--focus-ring`) in favour of NLDD's. It missed two consumers of `--radius-full`, and that shipped to `main` unnoticed until a grep found it days later (#202). ### Why it slipped through every gate A retired custom property fails silently by construction. `var(--gone)` with no fallback makes the whole declaration invalid, so the browser drops it - no error, no warning, no console output. `vue-tsc` does not look inside CSS variable names, ESLint does not parse `<style module>` values, and no test asserts on computed styles. The only signal is visual, and the two cases here were a `border-radius` on a small badge, which is exactly the kind of thing an eye skips. The same class of bug hit twice in that PR: the focus ring in #198 was a token that *did* exist but meant something else (`--semantics-focus-ring-box-shadow` is the gap, not the ring), which resolves to a valid value and is even harder to spot. ### Options 1. **A stylelint rule.** `declaration-property-value-no-unknown` does not cover this; a small custom rule or `stylelint-value-no-unknown-custom-properties` could check every `var(--x)` against the declared set (ours plus NLDD's `settings.css`). 2. **A CI grep** against a list of retired names. Cheap, catches renames, misses typos in new names. 3. **Require a fallback** on every `var()` in our own CSS, so a missing token degrades instead of dropping the declaration. Option 1 is the real fix; option 2 is a ten-line guard that would have caught this one. NLDD's own skill flags renamed tokens as "de meest gemiste val", so this is worth a guard rather than vigilance.
Author
Owner

Done, via option 3-and-then-some rather than option 1. Commit 2a4fd4b ("Fail the build and the tests on a silently broken design token") ships frontend/src/styles/nlddTokens.test.ts: it resolves every var() in src against the installed design system plus our own tokens, and fails on a reference that has neither a definition nor a fallback. Runs in 50ms, no browser, no stylelint dependency.

Stronger than the CI grep this issue sketched, because it checks against the tokens actually installed rather than against a hand-kept list of retired names - so it also catches a typo in a new name, and it moves with each NLDD bump.

Proven on input that should fail it: against the pre-fix tokens.css on 0.8.77 it names both offending lines. Two sibling checks landed in the same commit (a rendered-typography e2e assertion and a fontTuningPlugin self-check).

Done, via option 3-and-then-some rather than option 1. Commit 2a4fd4b ("Fail the build and the tests on a silently broken design token") ships frontend/src/styles/nlddTokens.test.ts: it resolves every var() in src against the installed design system plus our own tokens, and fails on a reference that has neither a definition nor a fallback. Runs in 50ms, no browser, no stylelint dependency. Stronger than the CI grep this issue sketched, because it checks against the tokens actually installed rather than against a hand-kept list of retired names - so it also catches a typo in a new name, and it moves with each NLDD bump. Proven on input that should fail it: against the pre-fix tokens.css on 0.8.77 it names both offending lines. Two sibling checks landed in the same commit (a rendered-typography e2e assertion and a fontTuningPlugin self-check).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
robbertbos/waggle#204
No description provided.