Skip to content

[FIR-316] Make component tokens overridable from an ancestor - #55

Merged
mfg1986 merged 6 commits into
developfrom
chore/FIR-316_overridable-component-tokens
Jul 20, 2026
Merged

[FIR-316] Make component tokens overridable from an ancestor#55
mfg1986 merged 6 commits into
developfrom
chore/FIR-316_overridable-component-tokens

Conversation

@mfg1986

@mfg1986 mfg1986 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What

Design-system components no longer declare their base custom-property tokens on their own selector; they consume them with a fallback:

// before — the declaration always won over any inherited value
.ff-panel { --ff-panel-bg: var(--ff-color-surface); background-color: var(--ff-panel-bg); }

// after — the property is left unspecified, so an ancestor can set it
.ff-panel { background-color: var(--ff-panel-bg, var(--ff-color-surface)); }

105 of 169 declarations converted across 21 files; 4 files already complied. No default value changed anywhere — every fallback is the literal that used to be declared.

Why

A custom property specified on an element always beats an inherited one, so declaring a component's tokens on its own block made them impossible to tune from a wrapping container. Found in FIR-293: .ff-data-table__page-size { --ff-select-min-width: 0 } had no effect because .ff-select re-declared 180px on itself, and the select overflowed its box by 84px onto the pagination controls. Jira: FIR-316

This lands before FIR-294 (visual parity gate) on purpose: porting the Flydocs theme will need per-component tuning, not just palette swaps.

Known limit (deliberate, documented)

Variant modifiers keep their declarations (.ff-panel--warning { --ff-panel-accent: … }). A variant picks the final value for that variant rather than offering a tunable default, and converting it would be a CSS cycle (--x: var(--x, …)). Measured in a browser: --ff-panel-accent on a warning panel is not overridable from an ancestor. Variants are retinted through the palette they resolve from. The README states this explicitly.

How to verify

  • pnpm nx run-many -t build lint test -p design-system design-system-contract playground — design-system 608 ✓ · contract 13 ✓ · playground 2 ✓ · lint 0 errors.
  • No visual regression: 31 catalog routes captured before and after in the same dev config; 30 byte-identical, the remaining one confirmed as render non-determinism (10px at Δ=1/255) by re-capturing the baseline hash twice.
  • Override proven in a real browser (jsdom resolves no custom properties, so the unit spec can only check this structurally): panel background #fffrgb(12,34,56), button radius 8px2px, select min-width 180px420px, all via getComputedStyle.
  • A spec scans every component stylesheet and fails if any rule both declares and consumes the same token — verified to catch a regression by reintroducing the old bug.

mfg1986 added 6 commits July 20, 2026 20:09
ff-panel, ff-banner, ff-toast, ff-input, ff-select, ff-dialog and
ff-button each re-declared their own default custom properties on the
selector that also consumed them, which always beats a value inherited
from an ancestor. Consume every token through var(token, default)
instead, keeping semantic variant blocks (color axis, per-type accents)
that intentionally provide the final value for that variant.
ff-radio, ff-chip, ff-checkbox, ff-progress, ff-icon-button and
ff-bottom-sheet each re-declared their own default custom properties on
the selector that also consumed them, which always beats a value
inherited from an ancestor. Consume every token through
var(token, default) instead, keeping the semantic variant block in
ff-progress that intentionally provides the final fill color per type.
…okens

ff-loader, ff-card, ff-tooltip, ff-skeleton, ff-link, ff-empty-state,
ff-avatar and ff-divider each re-declared their own default custom
properties on the selector that also consumed them, which always beats
a value inherited from an ancestor. Consume every token through
var(token, default) instead, so a wrapping container can retint or
resize any of these primitives.
…ancestor

Add an architectural spec that scans every component.scss file and
fails if a rule both declares and consumes the same --ff-* custom
property, which is exactly the pattern that shadows an ancestor's
override. Pair it with a behavioural spec that mounts ff-select inside
a host declaring --ff-select-min-width and checks the DOM wiring the
cascade rides on, documenting why jsdom's lack of CSS custom property
support rules out a getComputedStyle-based assertion here.
Explain why the design system never declares a default for its own
component tokens (a specified value always beats an inherited one) and
show the ff-select min-width override already used by ff-list as the
worked example for retinting/resizing a component from a wrapping
container.
@mfg1986

mfg1986 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

✅ Verdict: APPROVED (reviewer: mgarcia). Base component tokens are now overridable from an ancestor — proven in a real browser, not just structurally — with zero visual regression across 31 catalog routes captured before and after in the same dev config. The spec that scans every stylesheet for the declare-and-consume anti-pattern is a good guardrail against the pattern creeping back.

On acceptance criterion 1: 64 declarations remain inside variant modifier blocks, so the criterion is not met literally. Accepted as the correct scope — a variant picks the final value for that variant rather than offering a tunable default, converting it would be a CSS cycle, and variants resolve from the global palette, which is what the parity gate actually needs. The limit is measured and documented in the README rather than left implicit. If FIR-294 shows per-variant context overrides are genuinely needed, that becomes a follow-up with real requirements behind it.

CI green on the final head. Merging to develop.

@mfg1986
mfg1986 merged commit d0bb863 into develop Jul 20, 2026
4 checks passed
@mfg1986
mfg1986 deleted the chore/FIR-316_overridable-component-tokens branch July 20, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant