feat: add radix color theming (#133)

* chore: migrate tailwind v4 and add theme scaffolding

* feat: integrate radix colors and theme switching

* pnpm version
This commit is contained in:
Omar McAdam
2026-01-20 13:56:44 -08:00
committed by GitHub
parent b69ce75e58
commit 4689852686
37 changed files with 5331 additions and 2187 deletions

88
pr/ui-dark-radix.md Normal file
View File

@@ -0,0 +1,88 @@
# UI Dark Mode + Radix Colors
## Summary
Introduce system-aware dark mode support, persistable theme preferences, and a Radix Colors-driven palette that automatically swaps between light/dark without per-utility `dark:` modifiers. Upgrade Tailwind to v4 as the foundation.
## Goals / Non-goals
- Goals
- Upgrade Tailwind from v3 to v4 for the frontend.
- Add theme primitives that detect system preference, allow explicit user override, and persist choice.
- Integrate Radix Colors and map them into Tailwind so utilities like `bg-red-5` resolve to light/dark values automatically.
- Ensure the app renders correctly in desktop (Tauri) and web preview modes.
- Non-goals
- Redesign every screen or audit every color usage in this PRD; focus on system and tokens first.
- Add `dark:` variants to existing classes across the app.
- Introduce a new design system beyond Radix Colors and existing layout styles.
## Definitions
- Theme mode: `light`, `dark`, or `system` (follow OS preference).
- Theme attribute: The DOM attribute (e.g. `data-theme`) used to indicate the active theme.
- Radix scale: A 12-step scale (1-12) per color from `@radix-ui/colors`.
## Guiding principles
- Default to system preference when no explicit choice exists.
- Theme switching should be flicker-free and safe for app startup.
- Use CSS variables for color tokens so Tailwind utilities stay theme-agnostic.
- Keep the integration compatible with both Tauri webview and Vite dev server.
## Current state / problem
- Tailwind is currently v3 with a small custom config in `tailwind.config.ts`.
- Global styles set `color-scheme: dark` in `src/styles.css`, forcing dark UI even when the system is light.
- There are existing localStorage keys for mode preferences (e.g. `openwork.modePref` and `openwork_mode_pref`), but no centralized theme application logic.
- Some components use ad-hoc tone props (e.g. `tone="dark"`) instead of a global theme.
## Proposal
### 1) Tailwind v4 upgrade
- Upgrade to Tailwind v4 and switch to the Vite plugin (`@tailwindcss/vite`) for build performance.
- Remove the PostCSS plugin setup and `autoprefixer`, since v4 handles imports and prefixes.
- Migrate `@tailwind` directives to `@import "tailwindcss"` in the main stylesheet.
- Keep `tailwind.config.ts` only if needed, otherwise move theme tokens to CSS via `@theme`.
- If `tailwind.config.ts` is kept, add `@config` in the main stylesheet to load it explicitly.
- Audit for v4 breaking changes that affect existing classes (ring defaults, outline-none, shadow-sm, etc.).
### 2) Theme detection + persistence
- Add a dedicated theme module (e.g. `src/app/theme.ts`) that exposes:
- `mode()` signal: `light | dark | system`.
- `resolvedMode()` signal: `light | dark` (system-resolved).
- `setMode(next)` with persistence to localStorage (reuse `openwork.modePref`).
- Apply the initial theme before first paint (inline script or early bootstrap) to avoid flash.
- On app bootstrap, set a `data-theme` attribute on the `html` element based on `mode` + `window.matchMedia("(prefers-color-scheme: dark)")`.
- Listen for `(prefers-color-scheme: dark)` changes via the `matchMedia` listener when `mode === system` and update `data-theme` live.
- Update `color-scheme` on `html` to match the resolved theme for native controls.
- Use `window.matchMedia` for system detection in both Tauri and web; no Tauri-specific API required.
### 3) Radix Colors integration (no `dark:` modifiers)
- Install `@radix-ui/colors` and import CSS scales once in the global stylesheet.
- Use CSS variables to map Radix scales to Tailwind theme tokens.
- Light: `:root { --color-red-1: var(--red-1); ... }`
- Dark: `[data-theme="dark"] { --color-red-1: var(--red-dark-1); ... }`
- Define Tailwind colors to use these variables so utilities like `bg-red-5` map to `var(--color-red-5)`.
- Implement the mapping in CSS via Tailwind v4 `@theme` so utilities resolve without JS config.
- Provide a single source of truth for gray/mauve/slate scales to drive background, border, and text defaults.
## UX / flows
- Default behavior: on first launch, theme follows OS. No user action required.
- When a user selects a theme (light/dark/system), the choice is applied immediately and persisted.
- Theme changes should not cause page reloads; CSS variables should swap in-place.
## Data / storage
- Persist `mode` in localStorage under the existing key: `openwork.modePref`.
- Store only `light | dark | system` and remove legacy `openwork_mode_pref` over time.
## Migration
- Remove `color-scheme: dark` from `src/styles.css` and replace with a `color-scheme` that follows `data-theme`.
- Introduce global CSS variables for Radix colors and map Tailwind theme tokens to them.
- Replace ad-hoc `tone` usage where possible, or map it to the global theme state.
- Decide whether to keep `tailwind.config.ts` or fully migrate to CSS-based config to reduce upgrade friction.
## Acceptance criteria
- Tailwind v4 compiles successfully and the app boots in dev and Tauri.
- Theme defaults to system preference with no visible flash.
- Theme choice persists across restarts and applies on first paint.
- A class like `bg-red-5` renders different colors in light vs dark without `dark:` usage.
- Radix color scales (including grays) are available for text, background, border, and accent utilities.
## Open questions
- Should the theme preference be stored in localStorage only, or also in a project/workspace setting?
- Do we want a small UI toggle in the app settings now, or is this PRD limited to infra?
- Which Radix neutral scale should be the default base (gray vs slate vs mauve)?