Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.6 KiB
Spec: UI Overhaul — Stripe/Notion aesthetic, Tailwind + shadcn-style, light+dark
A full visual rebuild: clean, airy, modern, light-first with a polished dark mode. Rolled out incrementally — design system first, then page-by-page — so the app keeps working throughout.
1. Stack
Add to frontend:
- tailwindcss (+
postcss,autoprefixer) — utility styling. - Radix UI primitives (
@radix-ui/react-*: dialog, dropdown-menu, tabs, tooltip, switch, popover, toast, separator, scroll-area) — accessible behavior. - class-variance-authority + tailwind-merge + clsx — the shadcn component pattern.
- lucide-react — icon set (clean, consistent; replaces ad-hoc emoji where it helps).
Config:
tailwind.config.js— content globs overindex.html+src/**/*.{js,jsx}; theme extends map to CSS variables (below);darkMode: 'class'.postcss.config.js. Asrc/index.csswith@tailwind base/components/utilities+ the token:root/.darkblocks. Keep the oldstyles.cssimporting until a page is migrated, then drop per-page.
2. Design tokens (CSS variables, HSL)
Defined once in src/index.css; Tailwind theme references them so bg-background,
text-foreground, bg-primary, etc. just work and flip with .dark.
:root {
/* Neutrals — warm-tinted slate (Notion-ish paper) */
--background: 0 0% 100%;
--foreground: 222 22% 12%;
--card: 0 0% 100%;
--muted: 220 16% 96%;
--muted-foreground: 220 9% 46%;
--border: 220 16% 90%;
--input: 220 16% 90%;
--ring: 245 75% 60%;
/* Brand accent — indigo/iris (modern SaaS, Stripe-blurple cousin) */
--primary: 245 75% 59%; /* #5b5bf0-ish */
--primary-foreground: 0 0% 100%;
/* Semantic */
--success: 152 56% 40%;
--warning: 38 92% 50%;
--danger: 0 72% 51%;
--danger-foreground: 0 0% 100%;
--radius: 0.625rem; /* soft, modern corners */
}
.dark {
--background: 224 32% 9%; /* deep slate, not pure black */
--foreground: 220 18% 92%;
--card: 224 28% 12%;
--muted: 223 22% 17%;
--muted-foreground: 220 12% 64%;
--border: 223 20% 20%;
--input: 223 20% 22%;
--ring: 245 80% 66%;
--primary: 245 80% 67%;
--primary-foreground: 224 32% 9%;
--success: 152 50% 50%;
--warning: 38 92% 58%;
--danger: 0 70% 60%;
}
Accent is swappable by design. The accent lives in exactly one token (
--primary, plus its dark variant). Changing the brand color = editing those two lines. This also makes a future user-facing accent picker cheap: store a chosen hue on the user (or inlocalStorage) and write--primary/--ringat runtime. Decision (2026-06-30): ship with Indigo#5b5bf0; leave the picker as a documented future enhancement.
Proposed palette (for sign-off)
| Token | Light | Dark | Use |
|---|---|---|---|
| Primary (accent) | Indigo #5b5bf0 |
#7c7cf5 |
buttons, links, active nav, focus ring |
| Background | #ffffff |
#11151f |
app canvas |
| Card/surface | #ffffff |
#161b27 |
panels, cards |
| Muted surface | #f3f5f9 |
#1f2533 |
subtle fills, hover |
| Border | #e3e8ef |
#2b3242 |
hairlines |
| Text | #191e2b |
#e7eaf2 |
body |
| Muted text | #6b7280 |
#9aa3b2 |
secondary |
| Success | #2f9e6b |
#3dbd86 |
healthy, succeeded |
| Warning | #f5a623 |
#f7b84b |
caution, pending |
| Danger | #e23b3b |
#ef5a5a |
destructive, failed |
Alternatives if indigo isn't your taste (pick one and I'll swap the single token): Emerald
#10b981(calm, "clean"), Violet#7c3aed(premium), Teal#0d9488(fresh), Blue#2563eb(classic/trustworthy). Logo stays as-is; accent just needs to sit well beside it.
3. Component library (src/components/ui/)
Hand-built shadcn-style primitives, each a thin cva wrapper over Tailwind + (where
interactive) a Radix primitive:
button, card, input, textarea, select, checkbox, switch, badge,
dialog, sheet (side drawer), dropdown-menu, tabs, tooltip, toast (+ a
useToast hook to replace ad-hoc toast state), table, skeleton, separator,
avatar, empty-state.
Plus app-level shells: PageHeader, Sidebar, Topbar, StatCard,
ThemeToggle (writes .dark on <html>, persists to localStorage).
4. Layout language
- Sidebar: 248px,
bg-card, hairline border, grouped nav with section labels (Overview · Cleanup · Automation · Account). Lucide icons. Active item = soft primary tint pill. Collapsible to icon-rail on narrow widths. - Topbar: page title + breadcrumbs left; sync status, theme toggle, digest toggle, pending-review badge, "Sync now", avatar right. Sticky, subtle bottom border.
- Content: max-width container, generous padding (
p-6/p-8), cards withrounded-[--radius],border, soft shadow (shadow-sm), 16–24px gaps. - Density: comfortable default; tables get a compact variant for big lists.
- Motion: 150–200ms ease transitions on hover/expand; Radix-driven enter/exit on
dialogs/sheets/toasts. Respect
prefers-reduced-motion.
5. Page-by-page migration map
| Order | Page | Notes |
|---|---|---|
| 0 | Tooling + tokens + primitives | no visual swap yet; build the system |
| 1 | App shell (Layout.jsx) |
sidebar + topbar + theme toggle — biggest immediate lift |
| 2 | Dashboard | StatCards, chart cards restyled (keep chart.js, theme its colors via tokens) |
| 3 | Senders | list/detail split, new email rows, policy dropdown (ties to sender-policy spec) |
| 4 | Unsubscribe | table → new table primitive, confidence as colored badge/meter |
| 5 | Search / Folders / Cleanup | shared list components, filter bar, bulk toolbar restyle |
| 6 | New feature pages | Rules, Review queue, Screener, Activity, Privacy, Read-Later — built native |
| 7 | Landing | polish to match the new system |
| 8 | Retire styles.css |
delete once nothing imports it |
Each increment: convert the page, verify npm run build, screenshot/sanity-check, commit.
6. Accessibility & quality bar
- Radix primitives give focus management, ESC/overlay behavior, ARIA for free — don't hand-roll dialogs/menus.
- Visible focus ring (
--ring) on all interactive elements. - Color is never the only signal (icons/labels alongside semantic colors).
- Contrast ≥ WCAG AA in both themes for text and primary buttons.
- Keyboard shortcuts (already present) preserved and surfaced in a
?cheat-sheet dialog.
7. Out of scope
- No logo redesign (keeping current
Logo.jsx). - No mobile-dedicated layouts beyond responsive degradation (desktop-first decision).
- No new charting library (theme the existing chart.js).