Skip to content

Align dashboard palette with canonical tokens, add theme selector - #13

Merged
aharlap merged 6 commits into
mainfrom
design/dashboard-palette-and-theme
Aug 30, 2026
Merged

Align dashboard palette with canonical tokens, add theme selector#13
aharlap merged 6 commits into
mainfrom
design/dashboard-palette-and-theme

Conversation

@aharlap

@aharlap aharlap commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Proof's dashboard maintained its own invented palette rather than using the design system that already exists for it. prax/packages/tokens ships an MIT set that styles.ts's own header said to swap to, and prax-site/brand/brand.css:3 names "Proof UI" in its scope while assigning Proof a teal. The result was that the product and the marketing page selling it did not look like the same tool.

--praxity-* tokens sit outside the MIT boundary (prax/docs/open-source-boundary.md:45), so this adopts the canonical MIT set rather than copying brand.css. Relicensing the Proof brand subset remains open and this does not foreclose it.

Palette

  • Canonical tokens adopted name-for-name, so the block can later be replaced by an @praxity/tokens import.
  • Deleted three dead tokens: accent-3 (#FF48B0, the loudest value in the file, referenced nowhere), warn, and space.
  • Five hues reduced to three with one meaning each. Teal reads finished, yellow reads look here, neutral reads still going. Previously pink meant both "headline number" and "biggest drop-off" on the same screen.
  • The offset shadow was 1.10:1 against the page and read as printing misregistration rather than a device. Replaced with the canonical card shadow.
  • Bar tracks were 1.31:1 and effectively invisible. The accent fill now clears 3:1 against them, which is what makes the filled proportion legible.
  • Removed the h1::after rule: a 4px bar in a colour used nowhere else, at 1.99:1, encoding nothing.

Theme selector

Auto / Light / Dark in the header. Tokens are light-dark() pairs, so the selector only flips color-scheme on the root and native controls and scrollbars follow without extra rules. Auto honours prefers-color-scheme with no JS.

Served as /theme.js rather than an inline script because the CSP is default-src 'self' with no unsafe-inline — the header is unchanged. Blocking in <head>, so no flash. Public pages carry a pinned preference but have no control of their own. The control is hidden without script, so it is never a dead widget.

Type scale

Four sizes sat within 1.6px of each other (0.78/0.8/0.85/0.9rem), and the hero stat outranked the page title by 10% — close enough to read as a mistake rather than a hierarchy. Now 0.8 / 1 / 1.25 / 1.5 / 1.75 / 2rem and three weights instead of five. Help text moved up to body size since it is guidance people have to read.

Bugs fixed

Both found by the new dark-mode test, both would have affected real users:

  • input, select, button set a background but no color, so controls kept UA-default black ink on the dark canvas. WebKit ignores author color on a native menulist, so inputs and selects are now left to color-scheme; buttons do need explicit colours, because a UA button is silver #c0c0c0 and .prax-danger only reaches 3.61:1 against it.
  • Background was set on body only, leaving the root transparent, which made contrast tooling read the page as white.

Accessibility

Null table cells now carry a visually-hidden "None". A bare em dash can leave a cell sounding empty depending on punctuation level, and it was doing four different jobs (no score, no data, not applicable, no drop).

The drop-off row is a highlighter stroke on the words rather than a filled row — a full-width band was too loud on a dark page and put the teal bar directly on saturated yellow.

Verification

  • Every rendered text node measured against its resolved background in both themes: zero contrast failures. Tightest element sits 14% above threshold in light, 55% in dark.
  • Praxity Check: 7 pages x 2 states, 1267 rule evaluations, zero findings, zero needsReview, axe:color-contrast passed 14/14. The only 2 reported items are local-resource-missing artefacts of the static capture folder.
  • pnpm verify green: typecheck, 197 unit tests, 52 a11y tests across desktop/mobile x Chromium/WebKit — including a new test that pins a theme, follows it across navigation to a page with no selector, runs axe in dark, and confirms Auto resets and survives reload.
  • Theme select measures 64x28, clear of the WCAG 2.2 target-size minimum.

Known limits

  • The row tint behind the highlighter is imperceptible in light theme (1.03:1). Inherent: a pale yellow cannot separate from a near-white panel without returning to the cream look. The two marker strokes carry the signal instead.
  • axe:image-alt and svg-img-alt were inapplicable everywhere, so the bar chart and funnel were not tested as graphics. That is a coverage gap, not a pass.

🤖 Generated with Claude Code

aharlap and others added 6 commits August 29, 2026 19:59
The dashboard maintained its own invented palette: a warm cream base with
five accent hues, three of which were declared and never used. Meanwhile
prax/packages/tokens already ships an MIT set that this file's own header
said to swap to, and prax-site/brand/brand.css names Proof UI in its scope
and assigns Proof a teal. The product and the page selling it did not look
like the same tool.

Adopt the canonical tokens name-for-name so the block can later be replaced
by an @praxity/tokens import. Delete the dead accent-3, warn and space
tokens. Reduce five hues to three with one meaning each: teal reads
finished, yellow reads look here, neutral reads still going. The offset
shadow was 1.10:1 against the page and read as misregistration rather than
a device; it is now the canonical card shadow. Bar tracks were 1.31:1 and
effectively invisible; the accent fill now clears 3:1 against them.

Add a Theme control (Auto/Light/Dark). Tokens are light-dark() pairs, so
the selector only flips color-scheme on the root and native controls follow.
Served as /theme.js rather than inline because the CSP is default-src 'self'
with no unsafe-inline; blocking in <head> so there is no flash. Public pages
carry the preference but have no control of their own.

Unify the type scale. Four sizes sat within 1.6px of each other, and the
hero stat outranked the page title by 10%, close enough to read as a
mistake. Now 0.8/1/1.25/1.5/1.75/2rem and three weights.

Fix two real defects found while testing dark mode:
- input/select/button set a background but no color, so controls kept
  UA-default black ink on the dark canvas. WebKit ignores author color on a
  native menulist, so inputs and selects are left to color-scheme; buttons
  do need explicit colors because a UA button is silver and .prax-danger
  does not clear 4.5:1 against it.
- background was set on body only, leaving the root transparent, which made
  contrast tooling read the page as white.

Give null table cells a visually-hidden "None": a bare em dash can leave a
cell sounding empty depending on punctuation level.

Verified: every rendered text node measured in both themes, zero contrast
failures; Praxity Check over 7 pages x 2 states, 1267 evaluations, zero
findings and zero needsReview, axe:color-contrast passed 14/14.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dark capture was going to gitignored test-results/, so the only
screenshot anyone saw was light. Write it to docs/assets/ and show both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aharlap
aharlap merged commit 7a804ea into main Aug 30, 2026
5 checks passed
@aharlap
aharlap deleted the design/dashboard-palette-and-theme branch August 30, 2026 02:20
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