Canonical TS tokens, dark mode, Quarto export, contrast matrix CI#25
Merged
Merged
Conversation
This is the structural pass that makes @policyengine/ui-kit fit the "real design system" bar: a single source of truth, dark mode plumbed in, paper renders sharing the dashboard palette, and accessibility guarantees enforced by tests rather than convention. src/theme/tokens.ts is now the canonical source for every PolicyEngine design token. scripts/generate-css.ts reads it and writes: - src/theme/tokens.css → @import "@policyengine/ui-kit/theme.css" - src/theme/quarto.scss → @import "@policyengine/ui-kit/quarto.scss" Both files are checked in and CI fails if they drift from the generator output. Runtime exports - colors, palette, chartPalette, semanticFills, typography, radius, namedSpacing, breakpoints, contrastPairs, tokens — all available via @policyengine/ui-kit/tokens or the package root. - Consumers that need a JS hex string (Plotly traces, generated SVG, inline styles) no longer have to read the CSSOM. Consumers that need dark-mode-aware values keep using var(--color-…). Dark mode - :root.dark / .dark overrides for every shadcn semantic role plus accessible-on-dark text variants. Picked at WCAG AA on #0B0E14. - Activated by adding class="dark" to any ancestor (typically <html>); the @custom-variant dark declaration is unchanged. Quarto theme export - @policyengine/ui-kit/quarto.scss maps the same hex values to Bootstrap/Quarto SCSS ($primary, $body-color, $card-bg, link colors, accessible $pe-text-warning/error/success). Paper renders now share the dashboard's palette and contrast guarantees. WCAG contrast matrix in CI - tests/theme/contrast.test.ts implements the WCAG 2.x relative- luminance and contrast-ratio formulas and asserts every documented foreground/background pair clears AA in both light and dark mode. - tests/theme/generated-css.test.ts re-runs the generator and asserts the checked-in tokens.css and quarto.scss match — drift fails CI. - Two latent contrast bugs surfaced and were fixed: - --destructive #EF4444 → #DC2626 (red-500 → red-600); white foreground was 3.76:1, now 4.83:1. - --text-warning #d9480f → #c2410c (Mantine orange.9 → Tailwind orange-700); was 4.30:1 on white, now 5.18:1. Built-in focus-visible + reduced-motion at @layer base - :focus-visible outline rule on a, button, [role="button"], input, select, textarea, summary, [tabindex] using --ring. - @media (prefers-reduced-motion: reduce) snaps animations and transitions to instant. - Both are inherited by every consumer just by importing theme.css.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
Excellent work on this design system overhaul. The architecture is solid and achieves all stated goals:
Strengths:
- Single source of truth pattern is properly implemented (tokens.ts → generate-css.ts → CSS/SCSS)
- CI enforcement via generated-css.test.ts prevents drift
- WCAG contrast validation with proper formula implementation
- Clean separation: semanticFills (badges/UI) vs text-warning/error/success (accessible text)
- Dark mode properly structured with :root.dark overrides
- Runtime token exports for JS contexts (Plotly, etc.)
- Quarto SCSS integration for paper renders
Code Quality:
- WCAG luminance formula correctly implements spec
- Generator script is well-structured and maintainable
- Tests cover both contrast validation and generator drift
- TypeScript types properly exported
Minor style note (non-blocking):
There's some inconsistency in hex color casing (mostly uppercase like #FFFFFF, but a few lowercase like #f5f9ff, #e2e8f0, #ffffff, #5a5a5a, #c2410c). Both work fine, but consistent casing would be slightly cleaner.
Verified:
- The distinction between
--destructive(#DC2626, meets WCAG AA for white text) andsemanticFills.error(#EF4444, for badges) is intentional and documented - Quarto's `` mapping to semanticFills.error is appropriate for Bootstrap's use case
- All contrast pairs properly validate against their declared minimums
The prebuild hook ensures files stay in sync. Ready to merge.
This was referenced May 9, 2026
MaxGhenis
added a commit
that referenced
this pull request
May 9, 2026
…ix (#26) * Fix dark card visibility, Quarto $secondary, and expand contrast matrix Follow-up to #25 addressing review findings: - **Dark mode card surfaces visible.** `--card`/`--popover`/`--muted` bumped from #131820 (1.08:1 vs --background) to #1A2030 (1.19:1), and `--border`/`--input` bumped from #1E293B (1.32:1 / 1.11:1 on card) to #334155 (1.87:1 / 1.57:1). Cards and their borders are now distinguishable at glance instead of nearly invisible. - **Quarto `$secondary` fixed.** Was resolving to `--secondary-foreground` (#101828, near-black). Bootstrap's `$secondary` is a fill color, so paper renders previously got a near-black .btn-secondary background. Now uses `--secondary` (#F2F4F7), the light gray surface. - **Contrast matrix coverage.** Added pairs for: `--ring` on `--background` light + dark (WCAG SC 1.4.11 non-text 3:1 for focus indicators), dark `--primary-foreground` on `--primary`, dark `--destructive-foreground` on `--destructive`, and the default link color (teal-600) on background. - **`pretest` hook.** `bun run test` now regenerates `tokens.css` / `quarto.scss` before vitest runs, so token edits without an explicit `bun run generate-tokens` no longer surface as a misleading drift failure. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address review: revert pretest hook, fix old --border ratio comment The pretest hook (added in this PR) silently regenerates tokens.css before vitest runs, masking the drift test it was added to make less annoying. The test was specifically designed to catch hand-edits to the generated CSS; under pretest, those edits get clobbered before the assertion runs and CI goes green on drift. The error message ("run \`bun run generate-tokens\`") is already directive, so revert the hook and trust the existing UX. Also fix the inaccurate "1.11:1 on card" claim in the dark-mode --border comment — the actual ratio of #1E293B on #131820 is 1.22:1, and add a note next to the light-mode --ring documenting the SC 1.4.11 dependency (currently 3.51:1 on white, only ~0.5 above the 3:1 floor). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix changelog: 1.11 → 1.22 on card (matches tokens.ts comment) Round-2 review caught that the dark-card-visibility changelog fragment still claimed the old --border was "1.11:1 vs card" — the same number that was wrong in tokens.ts and got fixed there last commit but missed here. The actual #1E293B-on-#131820 ratio is 1.22:1. Tracking issue for the related --input SC 1.4.11 fail filed as #27. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Structural pass on the design system: makes
@policyengine/ui-kitfit the "real design system" bar — single source of truth, dark mode plumbed in, paper renders sharing the dashboard palette, accessibility guarantees enforced by tests instead of convention.What changes
Canonical TS source
src/theme/tokens.tsis now the source of truth for every token.scripts/generate-css.tsreads it and writes bothsrc/theme/tokens.css(for@import "@policyengine/ui-kit/theme.css") andsrc/theme/quarto.scss(for paper renders). Both files are checked in; CI fails if they drift.bun run generate-tokensregenerates the outputs;prebuildruns it automatically.Runtime token exports
colors,palette,chartPalette,semanticFills,typography,radius,namedSpacing,breakpoints,contrastPairs,tokens— all available via@policyengine/ui-kit/tokensor the package root.var(--color-…).Dark mode
:root.dark/.dark { … }overrides for every shadcn semantic role plus accessible-on-dark text variants (warning / error / success picked at WCAG AA on#0B0E14).class="dark"to any ancestor (typically<html>).Quarto theme export
@policyengine/ui-kit/quarto.scssmaps the same hex values to Bootstrap/Quarto variables ($primary,$body-color,$card-bg, link colors, accessible$pe-text-warning/$pe-text-error/$pe-text-success). Paper renders now share the dashboard's palette and contrast guarantees.WCAG contrast matrix in CI
tests/theme/contrast.test.tsimplements WCAG 2.x relative-luminance + contrast-ratio formulas and asserts every documented foreground/background pair clears AA in both light and dark mode.tests/theme/generated-css.test.tsre-runs the generator and asserts the checked-intokens.cssandquarto.scssmatch — drift fails CI.--destructive#EF4444→#DC2626(red-500 → red-600). White foreground was 3.76:1, now 4.83:1.--text-warning#d9480f→#c2410c(Mantine orange.9 → Tailwind orange-700). Was 4.30:1 on white, now 5.18:1.Focus + reduced motion at
@layer base:focus-visibleoutline rule ona, button, [role="button"], input, select, textarea, summary, [tabindex]using--ring.@media (prefers-reduced-motion: reduce)snaps animations and transitions to instant.theme.css.Verification
bun run typecheckcleanbun run test219/219 pass (including the 17 new contrast assertions and 2 generator-drift checks)bun run buildcleanNotes
chartColors(insrc/charts/chartDefaults.ts) is unchanged. The new indexed-slot helper is exported aschartPalette.🤖 Generated with Claude Code