feat: align semantic color tokens with the Figma export - #11
Open
anmolsinghbhatia wants to merge 4 commits into
Open
feat: align semantic color tokens with the Figma export#11anmolsinghbhatia wants to merge 4 commits into
anmolsinghbhatia wants to merge 4 commits into
Conversation
Audited all 203 tokens in each of the four theme files against variables.css, resolving both sides to sRGB and comparing values and primitive references. 29 tokens had drifted; this closes 26 of them. Priority is the substantive change. --priority-* was declared once at :root, so dark and dark-contrast rendered light-mode priority colors, and the light values were wrong at every step. It now resolves per theme and gains a sixth step, --priority-unlit, exposed as text-priority-unlit and border-priority-unlit. Figma's text/priority/* still points at the light ramp in both dark themes while icon/priority/* switches correctly, so the two disagree inside one theme. This follows icon/priority/* — a priority glyph and its label have to match — which leaves five tokens deliberately diverging from the export until Figma is fixed. Also corrected: the --neutral-800 (light) and --neutral-white (dark) primitives, which pulled text-disabled, text-on-color, text-icon-on-color, border-inverse and illustration-stroke-secondary off-spec; the light icon ladder, where text-icon-tertiary, text-icon-placeholder and text-icon-disabled each sat one rung too light, with the matching light-contrast overrides added; label steps for orange, pink, grey, indigo and purple; and the absent contrast overrides for text-warning-primary and label-purple-bg-strong. border-info-strong in dark-contrast drew from --blue-400, which sits at the dark end of the inverted dark ramp — a navy edge on a dark canvas. It now matches the other intents. --scrollbar-thumb-active drops from 40% to 30% alpha per the export, making the press state identical to hover, and the ladder comment no longer claims a fourth step. Three corrected values are below WCAG AA where the previous ones were above it, and are carried anyway because they are the published spec: label-orange-text on its own background in light is 3.00:1 (was 5.57:1), label-pink-text is 4.04:1 (was 5.50:1, and it already failed in dark), and --neutral-800 is 4.23:1 on white (was 4.54:1). The color-contrast rule is disabled on the two Badge Variants stories for the label pair, with the reason recorded inline. These need raising at source. PreviewCardEyebrowLabel moves from text-disabled to text-tertiary. It painted content with the one token exempt from contrast requirements, and only passed because --neutral-800 sat 0.04 above the AA threshold before this correction.
There was no colour story, so the tokens the audit corrected could only be reviewed by reading resolved values on the docs site. Five specimens cover the ladders where the gap between steps is the thing under review — priority, the text and icon ladders, status borders, labels, and the scrollbar thumb — plus an AllTokens story rendering the full namespace. Each family is painted the way it is consumed: backgrounds fill, text colours a glyph, borders draw an edge, ring and outline draw their indicator, and fill-* and stroke-* paint real SVG geometry. A flat grid of squares would show ring-danger-strong as a square rather than a ring. Utility names live in color-token-list.ts as literals rather than being assembled at runtime, because @theme inline inlines the semantic properties into utilities instead of emitting them: var(--text-priority-urgent) paints nothing, and Tailwind only generates a utility it has seen as a literal in the source. color-token-list.test.ts reads the @theme inline block and fails if the CSS gains or loses a token the list does not carry, so "every token" is checked rather than asserted in a comment. The story disables color-contrast. A specimen sheet is not an interface, and several of these tokens are meant to fail as running text — text-disabled is exempt by definition, text-priority-unlit is a dimmed indicator segment, and the label hues carry the Figma values verbatim. Themes come from the toolbar rather than side-by-side panels: the tokens are declared on :is(:root, :host), so a nested data-theme wrapper never re-resolves them and a four-up comparison in one canvas is not possible.
The colour page was hand-curated: ROLE_SPECS listed 41 of the 153 bg/text/border tokens, and the Status role covered fills only. So border-danger-strong shipped, resolved differently in all four themes, and appeared nowhere — while the prose two sections down referenced ring-danger-strong. Adds an "Every token" section built by enumerating the @theme inline block rather than listing names by hand: 227 tokens across eight families, alongside the 57 label tokens the existing grid already covered. It throws at build time if a token matches no family prefix, so a new utility namespace cannot ship undocumented. color-tokens.ts resolved light and dark only, which meant the contrast themes — the whole point of the audit — were invisible on the site. It now resolves all four by layering each variant block over its base the way the cascade does, and ColorRole, LabelGrid and the new ColorIndex all show four swatches. Values collapse to a single entry when a token resolves the same everywhere, so the common case does not read as four unrelated colours. literal() now accepts `transparent`: the scrollbar thumb is genuinely nothing at rest, and it is a real token the index has to show. The curated roles never hit that path. LABEL_TOKEN_COUNT is exported rather than computed in the page, because MDX inline expressions are markdown first — a `*` in {a.length * b.length} is escaped to `\*` by the formatter, which turns a working expression into a render-time syntax error that typechecking does not catch. Also notes the two label hues that do not clear AA on their own background.
codingwolf-at
approved these changes
Sep 1, 2026
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.
Description
Audited all 203 tokens in each of the four theme files from the Figma "Colors - Semantics" collection against
variables.css, resolving both sides to sRGB and comparing values and primitive references. 29 tokens had drifted; this closes 26.Priority is the substantive fix.
--priority-*was declared once at:root, so dark and dark-contrast rendered light-mode priority colors — and the light values were wrong at every step too. It now resolves per theme and gains a sixth step,--priority-unlit, exposed astext-priority-unlitandborder-priority-unlit.Also corrected: the
--neutral-800(light) and--neutral-white(dark) primitives, which pulledtext-disabled,text-on-color,text-icon-on-color,border-inverseandillustration-stroke-secondaryoff-spec; the light icon ladder, wheretext-icon-tertiary,text-icon-placeholderandtext-icon-disabledeach sat one rung too light, with the matching light-contrast overrides added; label steps for orange, pink, grey, indigo and purple; and the absent contrast overrides fortext-warning-primaryandlabel-purple-bg-strong.border-info-strongin dark-contrast drew from--blue-400, which sits at the dark end of the inverted dark ramp — a navy edge on a dark canvas. It now matches the other intents.--scrollbar-thumb-activedrops from 40% to 30% alpha per the export, making the press state identical to hover.Docs and Storybook now cover the whole namespace. The color page was hand-curated —
ROLE_SPECSlisted 41 of 153 bg/text/border tokens, and the Status role covered fills only — soborder-danger-strongshipped, resolved differently in all four themes, and appeared nowhere, while the prose referencedring-danger-strong. The page now lists every token, generated by enumerating@theme inlinerather than by hand, and resolves all four themes instead of light and dark. A newFoundations/Colorstory covers the same ground live, guarded by a test that fails if the CSS gains or loses a token the list does not carry.Three things need fixing in Figma
None of these block this PR, but the workarounds should go away once the source is corrected.
text/priority/*does not switch in dark modes. It still resolves topriority/light/*in Dark and Dark contrast whileicon/priority/*correctly switches topriority/dark/*, so the same semantic colour has two values inside one theme. This followsicon/priority/*— a priority glyph and its label have to agree — leaving five tokens deliberately diverging from the export.label-orange-texton its own background in light is 3.00:1 (was 5.57:1),label-pink-textis 4.04:1 (was 5.50:1, and it already failed in dark), and--neutral-800is 4.23:1 on white (was 4.54:1).color-contrastis disabled on the two BadgeVariantsstories for the label pair, with the reason recorded inline.scrollbar/thumb-activeequalsthumb-hoverat 30% in the export, so the press state is not distinguishable. Followed as published; worth confirming it is intended.Incidental fix
PreviewCardEyebrowLabelmoves fromtext-disabledtotext-tertiary. It painted content with the one token exempt from contrast requirements, and only passed a11y because--neutral-800sat 0.04 above the AA threshold before this correction.Type of Change
Screenshots and Media (if applicable)
Test Scenarios
Foundations/Color→Priority, switching all four themes: the ramp changes between light and dark, andunlitsits clearly belownonewithout dropping out of the surfaceFoundations/Color→StatusBordersin dark-contrast: the info edge is visible and consistent with the other intentsFoundations/Color→AllTokens: every family renders, no blank swatches, ring and outline draw indicators rather than squares@theme inlineand re-runpnpm test:color-token-list.test.tsfails with the missing namevariant="orange"orvariant="pink"in light: label text is the published Figma value, and reads as low-contrast by design pending the source fixPreviewCardwith an eyebrow label: the identifier text is one step muted from the description and no longer usestext-disabledpnpm checkandpnpm testgreen (1476 tests)References
No Plane work item — raised during a token audit of the design team's Figma export. Items 1–3 above need raising with design.
🤖 Generated with Claude Code