refactor(ui): semantic design-token system + hardened primitives#4
Merged
Conversation
Resolves the full component audit. The token layer was previously half-built (an unused --ring token, no semantic colors) and bypassed by every primitive; there was also no shared motion language. Tokens & theming: - Add semantic HSL tokens (primary/secondary/muted/accent/destructive + hover, border, input, ring) in globals.css, mapped in tailwind.config.js. Components now reference tokens instead of hardcoded Tailwind literals, giving a single source of truth. - Add a .dark token block (opt-in via class="dark"; light stays default). Kept outside @layer base so Tailwind does not tree-shake the selector. - Add a shared `ease-swift` cubic-bezier and enable darkMode: "class". Primitives: - Button: token colors, transform+shadow motion on ease-swift, active:scale-[0.98] press feedback, offset focus ring (ring-ring + ring-offset), icon slot (gap-2 + [&_svg]), inner top highlight on filled variants, disabled:cursor-not-allowed. - Card: token surface with machined depth (inner highlight + soft tinted shadow), concentric rounded-lg, CardTitle gains an `as` prop (no longer hardcodes <h3>), refined font-bold weight. - Input/Select/Textarea: unified --input border, offset focus ring, aria-[invalid=true] error state, shadow/transition parity. Select now renders a custom chevron (appearance-none) instead of the native arrow. - Badge: tokens + shape variant (pill default for counts, square option). - Separator: role/aria-orientation semantics + end-faded gradient. Usage sites: - Editor: dedicated color-input treatment; page title -> h1, sections -> h2. - Popup: square "New" badge; title -> h1. - Viewer: title -> h1, "Annotated Image" -> h2. Verified: npm run check green; confirmed token utilities and the .dark block compile into the built CSS. Co-Authored-By: Claude Opus 4.8 (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
Resolves the full component audit from the high-end-visual-design review. Two structural problems were the core: the token layer was half-built and bypassed by every primitive, and there was no shared motion language. Everything else was detail polish — all addressed here.
Tokens & theming
--primary/secondary/muted/accent/destructive+ hover,--border,--input,--ring) inglobals.css, mapped intailwind.config.js. Primitives now reference tokens instead of hardcodedemerald-700/slate-300literals → single source of truth..darktoken block, opt-in viaclass="dark"(light stays default). Deliberately kept outside@layer baseso Tailwind doesn't tree-shake the unreferenced.darkselector (verified it ships in the built CSS).ease-swiftcubic-bezier;darkMode: "class".Primitives
active:scale-[0.98], offset focus ring, icon slot (gap-2 [&_svg]), inner top highlight on filled variants,cursor-not-allowed.rounded-lg,CardTitlegains anasprop (no longer hardcodes<h3>), refined weight.--inputborder, offset focus ring,aria-[invalid=true]error state, shadow/transition parity. Select renders a custom chevron (appearance-none) instead of the native OS arrow.shapevariant (pill default for counts,squarefor labels).role/aria-orientation+ end-faded gradient.Usage sites
h1page title,h2sections.h1title.h1title,h2"Annotated Image".Verification
npm run check— typecheck + lint + 26 tests + build all greenbg-primary,border-input,ring-ring,ease-swift,aria-[invalid=true],active:scale-[0.98], …) and the.darkblock compile intodist/assets/globals.cssNotes / scope
slate-*utilities, so a fully-correct dark theme would be a follow-up. Default is unchanged (light).🤖 Generated with Claude Code