Skip to content

Designer tokens, a real dark palette, and readable contrast text - #98

Open
timurbazhirov wants to merge 2 commits into
mainfrom
claude/designer-tokens-and-dark-palette
Open

Designer tokens, a real dark palette, and readable contrast text#98
timurbazhirov wants to merge 2 commits into
mainfrom
claude/designer-tokens-and-dark-palette

Conversation

@timurbazhirov

Copy link
Copy Markdown
Member

The theme definition half of SOF-8024 portion 2. Everything below is measured, not eyeballed: src/theme/palette/contrast.ts is a thirty-line WCAG implementation, and tests/designerTokens.tests.ts holds every value to a threshold so a future edit that makes a stripe invisible fails the build instead of shipping.

1. The dark palette was missing most of itself

paletteDark spread only the primary, secondary and semantic colours. Keys this package declares on the palette in mui.d.tsborder, and in practice icon and unitTypes — were simply absent in dark mode.

palette.border.dark is dereferenced unguarded in three styled components:

File Expression
other/text-editor/TextEditor.styled.ts "1px solid " + theme.palette.border.dark
mui/components/custom/widgets/total-widget/TotalWidget.styled.ts 1px solid ${theme.palette.border.dark}
mui/components/custom/widgets/info-widget/InfoWidget.styled.ts 1px solid ${theme.palette.border.dark}

So they threw in dark mode. EntityName.tsx already carries theme.palette.border?.dark || "#ccc", which is the workaround this makes unnecessary. Demonstrated against the old and new palettes:

dark BEFORE: THROWS — Cannot read properties of undefined (reading 'dark')
dark AFTER : 1px solid #3D3D47

Dark now has its own surfaces, borders, icons, text and action colours, and a test asserts it carries every key the light palette declares.

2. contrastText was unreadable on three of the four semantic colours

contrastText is what MUI paints on main — a Button's label, a filled Chip's text. Measured against their own main:

Token Was Ratio Now Ratio
error.contrastText rgba(0, 0, 0, 0.23) 1.49 #FFFFFF 4.98
info.contrastText rgba(0, 0, 0, 0.23) 1.54 #000000 5.44
success.contrastText #FFFFFF 1.68 #000000 12.52
warning.contrastText #FFFFFF 3.11 #000000 6.75

An error button's label was 23%-opacity black on red. The translucency is why this went unnoticed by eye-checking the hex: scored as opaque black it looks fine, and the helper composites alpha before measuring for exactly that reason.

New successText covers the case neither success.main (1.68 on white) nor success.dark (2.18) can — the colour used as text rather than as a fill.

3. theme.designer.*

Additive namespace for what stock MUI has no vocabulary for, in light and dark:

  • unitType — all nine types (execution, subworkflow, map, assignment, condition, assertion, io, processing, error), each with its own readable label colour;
  • canvas — background, grid, wire, selection, insertAffordance;
  • state — modified, dirty, draft;
  • node — background, border, shadow, and minWidth (the readable floor an auto-fit should clamp to).

palette.unitTypes is now derived from the same source so the two cannot drift. Two of its values change: assignment #ff9800#B35C00 and condition #00BFA5#00786A. Those scored 2.15 and 2.14 against the white surface their card stripe sits on (wove's UnitCard.styled.tsx :before, and the dropdown swatch), below the 3:1 WCAG asks of non-text UI — which is why an assignment stripe was hard to pick out.

What this deliberately does not do

The brand hue is untouched. SOF-8024 portion 2 §2 gates the canonical palette on design/marketing sign-off — the platform says purple #5b37c0/#7c5fcd, the marketing site leans dark-navy and green. Swapping it later is an edit to palette/index.ts; nothing in designer.ts encodes the brand.

Tests

Also wires unit tests into npm test. tests/schemaUtils.tests.ts has been in the tree but never executedtest ran only lint && transpile. It passes; test:unit now runs both files.

29 tests, covering: WCAG scoring against known extremes, alpha compositing, every semantic contrastText, dark-palette completeness, every unit-type accent against the node it marks and against its own label, pairwise distinguishability of accents, state colours, canvas furniture (the wire must read, the grid must not), and the derived palette.unitTypes matching its source.

Two of these caught real mistakes in my own first draft — a white label on #0288D1 at 3.86, and a canvas wire at 2.60 — which is the point of writing them.


Generated by Claude Code

claude added 2 commits August 17, 2026 07:01
Three things, all measured rather than eyeballed — `src/theme/palette/contrast.ts` is a
thirty-line WCAG implementation, and `tests/designerTokens.tests.ts` holds every value
below to a threshold so a future edit that makes a stripe invisible fails the build.

**The dark palette was missing most of itself.** `paletteDark` spread only the primary,
secondary and semantic colours, so keys this package *declares* on the palette — border,
icon, unitTypes — were absent in dark mode. `palette.border.dark` is dereferenced
unguarded by TextEditor, TotalWidget and InfoWidget, so those threw; EntityName already
carried a `?.` and a hardcoded "#ccc", which was the workaround. Demonstrated before and
after: the same expression throws on the old palette and yields `#3D3D47` on the new one.
Dark now has its own surfaces, borders, icons, text and action colours.

**contrastText was unreadable on three of the four semantic colours.** An error button's
label was 23%-opacity black on red, scoring 1.49 where AA wants 4.5; info was 1.54 and
success 1.68. Each is now whichever of black or white actually reads (4.98–12.52). A new
`successText` covers the case neither `main` (1.68 on white) nor `dark` (2.18) can: the
colour used as text rather than as a fill.

**`theme.designer.*` is new** — unit-type accents for all nine types with their own label
colours, canvas background/grid/wire/selection, modified/dirty/draft state, and node
background/border/shadow/minWidth, in light and dark. The existing `palette.unitTypes` is
derived from the same source so the two cannot drift; its `assignment` and `condition`
values change, because `#ff9800` and `#00BFA5` scored 2.15 and 2.14 against the white
surface their card stripe sits on, below the 3:1 that non-text UI needs.

The brand hue is deliberately untouched: SOF-8024 portion 2 gates the canonical palette
on design/marketing sign-off, and swapping it later is an edit to one file.

Also wires the unit tests into `npm test`. `tests/schemaUtils.tests.ts` has been in the
tree but never executed, since `test` ran only lint and transpile; it passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FFFpkKD3zHysJXgAbT2uYV
…s-and-dark-palette

# Conflicts:
#	dist/theme/palette/index.d.ts
#	dist/theme/palette/index.js
#	dist/theme/theme.js
#	package.json
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.

2 participants