Skip to content

Status colours: exact Figma hues, AA text (Figma batch 2, project B)

Scope: web/src/styles/tokens.css tone tokens and the tone-*-fg/-accent consumers (Tag.tsx, StatCard.tsx, semanticTone.ts, WorkflowGraphView.tsx, Button.tsx’s danger variant). Audit rows 0.2, 0.3, 3.5 (docs/superpowers/specs/2026-09-25-figma-alignment-audit.md).

Dots, icons, borders/outlines and other decorative accents use Figma’s exact status hues. Text sitting on a light tint stays a darkened shade of the same hue so it clears WCAG AA (4.5:1) — Figma’s raw #ffa30e on its own tint is ~2:1, well under AA. Soft tints were already set in batch 1 and are unchanged here.

Figma hues (audit 0.2/0.3/3.5): amber #ffa30e, green #00b884, paid-label green #1a932e, red #ea3434, blue #5577ff, purple #9747ff.

Tone Accent (light/dark, pixel-exact) -fg (light) -fg (dark)
amber #ffa30e (was #f5a623) #a16300 (was #9a5b00) #ffa30e (was #ffc15e)
green #00b884 (was #1aa260) #00805c (was #0f7a45) #00b884 (was #5fd69a)
red #ea3434 (was #e0242a) #d11616 (was #b4232a) #ec4848 (was #ff8a8e)
blue #5577ff (was #3b82f6) #365eff (was #1d5fc9) #5d7eff (was #8ab6ff)
purple #9747ff (was #6a4b8d, i.e. tied to --primary) #892fff (was #6a4b8d) #ad6fff (was #d2b8f0)
green-solid #1a932e (was #1aa260) fg stays #ffffff fg stays #ffffff

Accent/fg are the same value in light and dark theme for amber/green/red/blue/purple — matching the pattern the file already used before this change (only purple used to differ, because it was piggybacking on --primary; it’s now its own hue like the rest). Every -accent token above is Figma’s exact hex, unadjusted — see “Round 1” below for why an earlier draft of this spec darkened red and green-solid, and why that was wrong.

Everywhere accents are used — dots, icons, plain borders/outlines, strokes, category pills — the value is the literal Figma hex. None of those consumers carry text of their own (verified by grep, see Round 1).

Round 1 fix: solid-fill tokens are separate from -accent

Section titled “Round 1 fix: solid-fill tokens are separate from -accent”

An earlier version of this change darkened tone-red-accent and tone-green-solid-accent themselves for AA, reasoning that they doubled as backgrounds behind white text. Review caught that this made the accent non-exact everywhere, including the plain decorative uses that never needed it. Fix: added two new tokens, used only by the one white-text consumer each (found by grepping every bg-tone-*-accent / bg-tone-*-soft use across web/src):

  • --tone-red-solid-bg: #e41818 (light and dark) — backs Button.tsx’s danger variant (bg-tone-red-solid-bg text-primary-fg), the only white-text consumer of red. Figma’s raw #ea3434 gives white text 4.17:1; darkened in lightness only (hue/sat kept) to 4.73:1. Same recipe the file already used for the previous batch’s red (#e5484d → #e0242a for 4.71:1).
  • --tone-green-solid-soft: #18892b (light and dark) — already existed as a token, was already equal to the darkened value; kept it there and pointed tone-green-solid-accent back at the exact Figma paid-label green (#1a932e, audit 3.5) instead. It backs Tag.tsx’s green-solid variant (bg-tone-green-solid-soft text-tone-green-solid-fg, white text), the only white-text consumer of green-solid. Raw #1a932e gives white text 3.99:1; the existing #18892b gives 4.51:1.

tone-red-accent and tone-green-solid-accent now stay Figma’s exact hex everywhere else: WorkflowGraphView.tsx’s uses of both (STRIPE map — a 5px decorative bar down a node’s left edge, arrow-marker fills, stroke-tone-red-accent edges/circles) turned out to carry no text at all — node title/subtitle text is fill-fg/fill-fg-muted (theme text colour), not a tone colour — so they needed no adjustment and were reverted to exact. Tag.tsx’s plain red tone (outline/soft) and every border/aria-invalid use of tone-red-accent (Combobox, Select, Input, MoneyInput, CloseDialog, ManualJournalPage, etc.) pair the accent with tone-red-fg text, never white, so they were already fine and are unaffected.

Contrast ratios (computed, WCAG relative-luminance formula)

Section titled “Contrast ratios (computed, WCAG relative-luminance formula)”

Light theme, -fg vs. its soft tint / vs. white:

Tone fg vs. tint fg vs. white
amber 4.53:1 4.88:1
green 4.51:1 4.95:1
red 4.52:1 5.47:1
blue 4.53:1 5.00:1
purple 4.51:1 5.36:1

Dark theme, -fg vs. its (dark) soft tint / vs. --surface:

Tone fg vs. tint fg vs. surface
amber 7.91:1 8.75:1
green 5.96:1 6.83:1
red 4.52:1 4.66:1
blue 4.53:1 4.92:1
purple 4.52:1 5.42:1

Amber/green in dark theme already clear 4.5:1 using the exact Figma hue as -fg (no darkening needed — the accent itself is bright enough against the dark tint/surface), so -fg there equals -accent. Red/blue/purple needed a small lightening of the same hue to clear the bar on dark backgrounds.

White-text solid fills (the dedicated tokens, not -accent):

Token vs. white
tone-red-solid-bg (danger button) 4.73:1 (light and dark, same value)
tone-green-solid-soft (Tag green-solid chip) 4.51:1 (light and dark, same value)

Also checked: tone-purple-fg on --primary-soft (Button.tsx’s secondary variant, bg-primary-soft text-tone-purple-fg) — this pair happens to be numerically identical to tone-purple-fg vs. tone-purple-soft already in the table above, because --primary-soft and --tone-purple-soft are the same value in both themes (#f1e9f1 light, #2a2152 dark). Light 4.51:1, dark 4.52:1 — already ≥4.5:1, no token change needed; added as its own explicit test case per review request.

All ratios are enforced by web/src/drift/contrast.test.ts, which parses tokens.css and asserts every pair above stays ≥4.5:1. Verified as real guards, not vacuous: broke tone-amber-fg back to the raw (non-AA) Figma hex and watched the -fg test fail (1.85 < 4.5); in round 1, broke tone-red-solid-bg back to the raw (non-AA) Figma red and watched that test fail (4.17 < 4.5) too. Restored both after.

  • Approved vs. paid chip colour. Figma’s audit (row 3.5) shows a different KPI label green for 已批准 (#00b884) vs. 已付款 (#1a932e), but a previous batch already made this a deliberate, documented product decision (StatCard.tsx: “distinguished only by their icon and label text, never a new colour”, flagged [PD] in the audit) and confirmed against the live Figma renders (123:7183, 407:17221) — both are the same plain green outline/soft chip in the actual screens, just a different icon (check vs. cash) and label text. This task is colour-only and doesn’t reopen that decision; #1a932e is used instead for tone-green-solid, the one place a second, distinct green already existed in the codebase (Tag.tsx’s green-solid variant, currently reachable only from the dev/ui.tsx kitchen sink — no live screen renders it today).
  • Soft tints — unchanged, per batch 1.
  • Teal, neutral, muted tones — not in the Figma exact-colour list for this audit row; left as-is.
  • No component logic changed. Tag.tsx, StatCard.tsx, semanticTone.ts, WorkflowGraphView.tsx all consume tokens by name; only the CSS values moved.

Initial pass:

  • pnpm -C web typecheck && pnpm -C web lint — clean (16 pre-existing react-refresh/only-export-components warnings, unrelated to this change).
  • pnpm -C web exec vitest run src/drift/ and the Tag/StatCard/semanticTone/claims/ payout/reports suites — pass.
  • Full pnpm -C web test is flaky under full parallel load on this branch before this change too (confirmed via git stash at the time, before the “don’t stash” rule for shared worktrees; a temporary commit would be used instead now): same ChannelEditor/ClaimWizard/ReportsPage timeouts reproduce on the unmodified tree; the same files pass individually or in a smaller batch. Not caused by this change.
  • Screenshots of Figma nodes 123:7183 and 407:17221 (fetched with get_screenshot, saved to the scratchpad) confirm dot/chip placement and colour families for 待审核/已批准/已付款/总提交, category pills, and 批准/驳回 buttons.

Round 1 (this fix), machine under load — ran only the affected files:

  • pnpm -C web typecheck && pnpm -C web lint — clean, same 16 pre-existing warnings.
  • pnpm -C web exec vitest run src/drift/contrast.test.ts src/drift/nohex.test.ts src/components/ui/Tag.test.tsx src/components/ui/StatCard.test.tsx src/components/ui/Button.test.tsx — 5 files, 31 tests, all pass (Button.test.tsx’s danger class assertion updated from bg-tone-red-accent to bg-tone-red-solid-bg to match the rename; WorkflowGraphView.tsx has no dedicated test file to run).
  • Watched the two new/changed guards fail once each: tone-red-solid-bg reverted to the raw non-AA red (4.17 < 4.5, failed as expected) and tone-amber-fg reverted to the raw non-AA amber (1.85 < 4.5, failed as expected), both restored after.