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).
Decision (user-approved)
Section titled “Decision (user-approved)”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.
What changed in tokens.css
Section titled “What changed in tokens.css”| 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) — backsButton.tsx’sdangervariant (bg-tone-red-solid-bg text-primary-fg), the only white-text consumer of red. Figma’s raw#ea3434gives 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→#e0242afor 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 pointedtone-green-solid-accentback at the exact Figma paid-label green (#1a932e, audit 3.5) instead. It backsTag.tsx’sgreen-solidvariant (bg-tone-green-solid-soft text-tone-green-solid-fg, white text), the only white-text consumer of green-solid. Raw#1a932egives white text 3.99:1; the existing#18892bgives 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.
What did not change
Section titled “What did not change”- 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;#1a932eis used instead fortone-green-solid, the one place a second, distinct green already existed in the codebase (Tag.tsx’sgreen-solidvariant, currently reachable only from thedev/ui.tsxkitchen 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.tsxall consume tokens by name; only the CSS values moved.
Verification
Section titled “Verification”Initial pass:
pnpm -C web typecheck && pnpm -C web lint— clean (16 pre-existingreact-refresh/only-export-componentswarnings, 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 testis flaky under full parallel load on this branch before this change too (confirmed viagit stashat 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:7183and407:17221(fetched withget_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’sdangerclass assertion updated frombg-tone-red-accenttobg-tone-red-solid-bgto match the rename;WorkflowGraphView.tsxhas no dedicated test file to run).- Watched the two new/changed guards fail once each:
tone-red-solid-bgreverted to the raw non-AA red (4.17 < 4.5, failed as expected) andtone-amber-fgreverted to the raw non-AA amber (1.85 < 4.5, failed as expected), both restored after.