Skip to content

fix(theme): make theme token references resolve (borders, prose colours, focus ring) - #205

Merged
JakeGinnivan merged 3 commits into
mainfrom
jakeginnivan/fix-border-tokens
Oct 16, 2026
Merged

JakeGinnivan merged 3 commits into
mainfrom
jakeginnivan/fix-border-tokens

Conversation

@JakeGinnivan

Copy link
Copy Markdown
Contributor

What was broken

The theme builder used flat dotted keys ('border.default'). Panda emits those as --colors-border\.default, but every reference to them ({colors.border.default}, token(colors.border.subtle)) resolves to var(--colors-border-default), which was never defined. Those references silently computed to nothing:

  • border="default" | "subtle" | "emphasis", the 6px sponsor border and the admin borders rendered as no border
  • every prose colour on MDX/blog pages (body, headings, links, quotes, rules) fell back to the inherited colour
  • the bg.*, button.* and link.* aliases were empty
  • token(colors.border.subtle) dividers in the header and modals vanished

Utility props like bg="surface.card" kept working, because the class and the definition used the same escaped name. That's why it went unnoticed.

Separately, focus-ring was 0 0 0 3px {colors.interactive.focus}33. Once the reference becomes var(...), the 33 is a stray number rather than a hex alpha, so the whole box-shadow was invalid and inputs using it showed no ring.

The fix

  • Nest the dotted keys in theme-builder.ts (a small nest() helper) so Panda defines the hyphenated names that references use. The two places that hard-coded the escaped name (body background, hero fade) now use the hyphenated one.
  • Express the focus-ring alpha with color-mix(... 20%, transparent) in all four themes (fork + stub).
  • Remove colorPalette="brand.primary|secondary" from 8 buttons. It stops type-checking once brand is nested, and it was always a no-op: it only set --colors-color-palette, while the button recipe reads colorPalette.solid.*, which brand never had. Those buttons inherited gray from html before and still do.
  • New app/theme/token-references.test.ts fails on any colour/border/shadow var the generated CSS references but never defines, and on any dot-escaped token name. Verified it fails without the fix.

Visible changes

Baselines were regenerated with pnpm vr --update-snapshots=all, because several changes sit under the 2% tolerance. I reviewed before/after crops of each route:

  • home: Important Dates rows gain their 2px emphasis top border (+12px page height)
  • content page, blog post: prose colours apply. Link-wrapped headings, links, list markers and the blockquote rule take text.highlight (5.6:1 dark, 5.4:1 light, calculated rather than measured with axe)
  • sponsors, agenda: sponsor tiles gain their 6px border.sponsor gap
  • talk detail: the dialog's title divider renders
  • blog index: unchanged

The admin and portal pages also get their card borders back. Those pages aren't in the visual suite.

Worth a look: MDX headings are anchor links, so they now take the link colour instead of white. If we'd rather they stay white, it's a one-line prose override.

Testing

  • tsc clean, 814 unit tests pass, pnpm vr 81/81 on the new baselines
  • Did not run the axe e2e suite locally

Core change, so this needs a /core-push after merge.

🤖 Generated with Claude Code

… use

The theme builder used flat dotted keys ('border.default'). Panda emits
those as `--colors-border\.default`, but every reference to them
(`{colors.border.default}`, `token(colors.border.subtle)`) resolves to
`var(--colors-border-default)`, which was never defined. The references
silently computed to nothing:

- the `default`, `subtle`, `emphasis`, `sponsor` and admin border tokens
  rendered as no border
- every prose colour on MDX pages (body, headings, links, quotes, rules)
  fell back to the inherited colour
- the `bg.*`, `button.*` and `link.*` aliases were empty
- `token(colors.border.subtle)` dividers in the header and modals vanished

Nest the keys so Panda defines the hyphenated names. The two places that
hard-coded the escaped name (body background, hero fade) move to the
hyphenated one.

`colorPalette="brand.primary"` stops type-checking, because a nested
`brand` group no longer yields a `brand.primary` palette. The prop was
always a no-op: it only set `--colors-color-palette`, while the button
recipe reads `colorPalette.solid.bg` and friends, which brand never had,
so those buttons inherited the gray palette from `html`. Removing it
keeps them exactly as they rendered.

New test fails on any colour, border or shadow var the generated CSS
references but never defines, and on any dot-escaped token name.
`0 0 0 3px {colors.interactive.focus}33` meant a hex alpha suffix, but
the reference becomes `var(...)`, and `var(...)33` is a var followed by
a stray number. The whole declaration was invalid, so inputs styled with
`boxShadow: 'focus-ring'` showed no ring. Express the 20% alpha (0x33)
with color-mix instead.
Regenerated with `pnpm vr --update-snapshots=all`, since several of the
changes sit under the 2% tolerance and a plain update would skip them.

- home: Important Dates rows gain their 2px emphasis top border (+12px)
- content page, blog post: prose colours apply, so link-wrapped headings,
  links, list markers and the blockquote rule take `text.highlight`
- sponsors, agenda: sponsor tiles gain their 6px `border.sponsor` gap
- talk detail: the dialog's title divider now renders

Blog index is unchanged.
@JakeGinnivan
JakeGinnivan merged commit 5c2915f into main Oct 16, 2026
3 checks passed
@JakeGinnivan
JakeGinnivan deleted the jakeginnivan/fix-border-tokens branch October 16, 2026 02:43
JakeGinnivan added a commit to dddwa/ddd-core that referenced this pull request Oct 19, 2026
Upstreamed from dddwa/dddperth#205.

The theme builder used flat dotted keys ('border.default'). Panda emits
those as `--colors-border\.default`, but every reference to them
(`{colors.border.default}`, `token(colors.border.subtle)`) resolves to
`var(--colors-border-default)`, which was never defined. Those
references silently computed to nothing: the border tokens rendered as
no border, prose colours fell back to the inherited colour, the
`bg.*`/`button.*`/`link.*` aliases were empty, and `token()` dividers
vanished.

- Nest the keys so Panda defines the hyphenated names; move the two
  hard-coded escaped references (body background, hero fade) over.
- `focus-ring` was `{colors.interactive.focus}33`, which becomes
  `var(...)33`: an invalid box-shadow, so no ring showed. Use color-mix
  for the 20% alpha in both stub themes.
- Drop `colorPalette="brand.primary|secondary"` from the voting,
  agenda, my agenda and feedback buttons. It no longer type-checks once `brand` is nested, and it was
  always a no-op: the button recipe reads `colorPalette.solid.*`, which
  brand never had, so the buttons inherited gray and still do.
- New test fails on any referenced-but-undefined colour, border or
  shadow var, and on any dot-escaped token name.

Stub baselines regenerated with `pnpm vr --update-snapshots=all`: home
gains the Important Dates top border, prose links and anchor headings
take `text.highlight`, sponsor tiles get their `border.sponsor`
frame, and the talk dialog's title divider renders.
JakeGinnivan added a commit to dddwa/ddd-core that referenced this pull request Oct 19, 2026
Upstreamed from dddwa/dddperth#205.

The theme builder used flat dotted keys ('border.default'). Panda emits
those as `--colors-border\.default`, but every reference to them
(`{colors.border.default}`, `token(colors.border.subtle)`) resolves to
`var(--colors-border-default)`, which was never defined. Those
references silently computed to nothing: the border tokens rendered as
no border, prose colours fell back to the inherited colour, the
`bg.*`/`button.*`/`link.*` aliases were empty, and `token()` dividers
vanished.

- Nest the keys so Panda defines the hyphenated names; move the two
  hard-coded escaped references (body background, hero fade) over.
- `focus-ring` was `{colors.interactive.focus}33`, which becomes
  `var(...)33`: an invalid box-shadow, so no ring showed. Use color-mix
  for the 20% alpha in both stub themes.
- Drop `colorPalette="brand.primary|secondary"` from the voting,
  agenda, my agenda and feedback buttons. It no longer type-checks once `brand` is nested, and it was
  always a no-op: the button recipe reads `colorPalette.solid.*`, which
  brand never had, so the buttons inherited gray and still do.
- New test fails on any referenced-but-undefined colour, border or
  shadow var, and on any dot-escaped token name.

Stub baselines regenerated with `pnpm vr --update-snapshots=all`: home
gains the Important Dates top border, prose links and anchor headings
take `text.highlight`, sponsor tiles get their `border.sponsor`
frame, and the talk dialog's title divider renders.
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