Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
15 KiB
Technology Stack
Project: Ready2Blob v1.2 — UI Polish & MD3 Overhaul Researched: 2026-03-31 Scope: Stack ADDITIONS for Material Design 3, dark mode, accent colors, responsive improvements Existing stack (validated, not re-researched): Vite 6, React 18, TypeScript 5, Tailwind v4 (@tailwindcss/vite), react-hook-form 7, Zod 4, Vitest 4, JSZip
Recommendation: Zero New Runtime Dependencies
The v1.2 UI overhaul should be achieved with Tailwind v4's native theming system + hand-authored MD3 design tokens in CSS. No component library. No runtime theming library. The existing approach (semantic HTML + Tailwind utilities) is the right foundation -- it just needs a proper token system layered on top.
Rationale: The app currently has zero component library dependencies and 159 passing tests. Introducing a component library (Material Tailwind, MUI, shadcn/ui) at this stage would:
- Require rewriting every existing component to match the library's API
- Break existing tests that assert on current DOM structure
- Add bundle weight for a wizard that needs at most 6-8 component types
- Create upgrade debt for a library the team doesn't control
Instead: define MD3 tokens as CSS custom properties, wire them into Tailwind v4's @theme directive, and build the small set of reusable patterns (card, input, button, elevation) as project-owned Tailwind utility compositions.
New Stack Additions
Design Token Generation (Dev Dependency Only)
| Technology | Version | Purpose | Why |
|---|---|---|---|
@material/material-color-utilities |
0.4.0 | Generate MD3 color palettes from seed color | Official Google library. Used at build/dev time via a small script to generate light + dark token sets from a single seed color. NOT bundled into the app -- it produces static CSS custom properties. This is the same algorithm the Material Theme Builder uses. HIGH confidence (official Google package, actively maintained). |
How it works: Write a one-time Node script (scripts/generate-theme.ts) that:
- Takes a seed color (hex)
- Uses
themeFromSourceColor()to generate full MD3 palette (primary, secondary, tertiary, error, surface, outline, etc.) - Outputs CSS custom properties in the
--md-sys-color-*naming convention - Writes to
src/theme-tokens.csswhich is imported intosrc/index.css
This means the generated tokens are static CSS -- zero runtime cost, zero bundle impact from the color library.
Tailwind v4 Theme Integration (No New Dependency)
| Technology | Version | Purpose | Why |
|---|---|---|---|
Tailwind v4 @theme directive |
Already installed | Map MD3 tokens to Tailwind utility classes | Tailwind v4's @theme directive creates utility classes from CSS custom properties. Defining --color-primary, --color-surface, etc. in @theme blocks automatically generates bg-primary, text-on-primary, bg-surface utilities. No config file needed -- pure CSS. HIGH confidence (verified in official Tailwind v4 docs). |
Tailwind v4 @custom-variant |
Already installed | Class-based dark mode toggle | Tailwind v4 replaces the old darkMode: 'class' config with @custom-variant dark (&:where(.dark, .dark *)); in CSS. This enables the dark: prefix to respond to a .dark class on the HTML element. HIGH confidence (verified in official Tailwind v4 dark mode docs). |
No Other New Dependencies
| Category | Decision | Rationale |
|---|---|---|
| Component library | Do NOT add | Current semantic HTML + Tailwind is correct. MD3 styling is achieved through tokens + utility classes, not through library components. |
| CSS-in-JS | Do NOT add | Tailwind v4 handles everything via CSS. Adding styled-components or Emotion would conflict with the existing Tailwind approach. |
| Theme toggle library (next-themes) | Do NOT add | next-themes is Next.js-focused. For a pure Vite SPA, a 15-line React hook (useTheme) with localStorage + classList.toggle is all that's needed. |
| Animation library | Do NOT add | MD3 motion tokens (duration, easing) are CSS custom properties. Tailwind v4's @theme can define transition tokens. No framer-motion or similar needed for the subtle transitions in a wizard UI. |
| Icon library | Evaluate later | If MD3 icons are desired, @material-design-icons/svg provides tree-shakeable SVGs. But this is a nice-to-have, not a v1.2 blocker. |
Detailed Integration Plan
1. MD3 Color Token System
The Material Design 3 color system uses ~29 semantic color roles (not raw palette values). These map to CSS custom properties:
/* Light theme tokens (generated from seed color) */
:root {
--md-sys-color-primary: #006A6A;
--md-sys-color-on-primary: #FFFFFF;
--md-sys-color-primary-container: #6FF7F6;
--md-sys-color-on-primary-container: #002020;
--md-sys-color-secondary: #4A6363;
--md-sys-color-on-secondary: #FFFFFF;
--md-sys-color-surface: #FAFDFC;
--md-sys-color-on-surface: #191C1C;
--md-sys-color-surface-container: #EFF2F1;
--md-sys-color-surface-container-low: #F4F7F6;
--md-sys-color-surface-container-high: #E9ECEB;
--md-sys-color-outline: #6F7979;
--md-sys-color-outline-variant: #BEC9C8;
--md-sys-color-error: #BA1A1A;
--md-sys-color-on-error: #FFFFFF;
/* ... ~29 roles total */
}
/* Dark theme tokens (same seed, dark scheme) */
.dark {
--md-sys-color-primary: #4EDADA;
--md-sys-color-on-primary: #003737;
--md-sys-color-surface: #101414;
--md-sys-color-on-surface: #E0E3E2;
/* ... all roles overridden */
}
2. Tailwind v4 Theme Wiring
/* src/index.css */
@import "tailwindcss";
@import "./theme-tokens.css"; /* Generated MD3 tokens */
@custom-variant dark (&:where(.dark, .dark *));
@theme {
/* Map MD3 tokens to Tailwind color utilities */
--color-primary: var(--md-sys-color-primary);
--color-on-primary: var(--md-sys-color-on-primary);
--color-primary-container: var(--md-sys-color-primary-container);
--color-on-primary-container: var(--md-sys-color-on-primary-container);
--color-secondary: var(--md-sys-color-secondary);
--color-on-secondary: var(--md-sys-color-on-secondary);
--color-surface: var(--md-sys-color-surface);
--color-on-surface: var(--md-sys-color-on-surface);
--color-surface-container: var(--md-sys-color-surface-container);
--color-surface-container-low: var(--md-sys-color-surface-container-low);
--color-surface-container-high: var(--md-sys-color-surface-container-high);
--color-outline: var(--md-sys-color-outline);
--color-outline-variant: var(--md-sys-color-outline-variant);
--color-error: var(--md-sys-color-error);
--color-on-error: var(--md-sys-color-on-error);
/* MD3 Shape scale */
--radius-xs: 4px;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 28px;
--radius-full: 9999px;
/* MD3 Elevation (box-shadows) */
--shadow-elevation-0: none;
--shadow-elevation-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-elevation-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
--shadow-elevation-3: 0 1px 3px 0 rgb(0 0 0 / 0.3), 0 4px 8px 3px rgb(0 0 0 / 0.15);
--shadow-elevation-4: 0 2px 3px 0 rgb(0 0 0 / 0.3), 0 6px 10px 4px rgb(0 0 0 / 0.15);
--shadow-elevation-5: 0 4px 4px 0 rgb(0 0 0 / 0.3), 0 8px 12px 6px rgb(0 0 0 / 0.15);
/* MD3 Motion tokens */
--animate-md3-enter: md3-enter 0.2s cubic-bezier(0, 0, 0, 1);
--animate-md3-exit: md3-exit 0.15s cubic-bezier(0.3, 0, 1, 1);
@keyframes md3-enter {
from { opacity: 0; transform: scale(0.92); }
to { opacity: 1; transform: scale(1); }
}
@keyframes md3-exit {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.92); }
}
}
Usage in components then becomes natural Tailwind:
<div className="bg-surface-container rounded-md shadow-elevation-1 dark:shadow-elevation-2">
<h2 className="text-on-surface">Step Title</h2>
<button className="bg-primary text-on-primary rounded-full px-6 py-2">
Next
</button>
</div>
3. Dark Mode Toggle Hook
No library needed. A simple React hook:
// src/hooks/useTheme.ts
function useTheme() {
const [theme, setTheme] = useState<'light' | 'dark' | 'system'>(() => {
return (localStorage.getItem('theme') as 'light' | 'dark') ?? 'system';
});
// Toggle .dark class on <html>, persist to localStorage
// Listen to prefers-color-scheme for 'system' mode
}
4. Accent Color System
For user-selectable accent colors, the approach is:
- Offer 3-5 preset seed colors (not arbitrary color picker)
- Pre-generate token sets for each seed color at build time
- Switch accent by swapping a CSS class on
<html>that loads a different set of--md-sys-color-*variables
This avoids runtime color generation (which would require bundling @material/material-color-utilities).
Alternatives Considered
| Category | Recommended | Alternative | Why Not |
|---|---|---|---|
| Component library | None (keep semantic HTML + Tailwind) | Material Tailwind v3 | Material Tailwind is React + Tailwind but: (a) requires rewriting all existing components, (b) v3 is still in pre-order/beta, (c) adds ~50KB+ bundle weight for components the wizard doesn't need. Not worth the rewrite cost for 6-8 component types. |
| Component library | None | MUI (Material UI) | MUI uses Emotion CSS-in-JS, fundamentally conflicts with Tailwind. Would require ripping out Tailwind entirely. Wrong direction. |
| Component library | None | shadcn/ui | Good library but opinionated toward Radix primitives. Adding it now means learning a new component API while also implementing MD3 tokens. For v1.2 scope (cards, inputs, buttons, toggles), hand-authored Tailwind components are faster and simpler. |
| Color generation | @material/material-color-utilities (dev-only) |
m3-tailwind-colors npm package |
Only 3 GitHub stars, single maintainer, uncertain maintenance. The underlying @material/material-color-utilities is the official Google package -- better to use it directly with a small script than depend on a wrapper. |
| Color generation | Build-time script | Runtime @material/material-color-utilities in bundle |
Adds ~30KB to the client bundle for something that only needs to run once per accent color change. Pre-generate at build time instead. |
| Dark mode toggle | Custom 15-line hook | next-themes | next-themes is designed for Next.js SSR hydration edge cases. For a Vite SPA, it's unnecessary complexity. The core logic is classList.toggle('dark') + localStorage. |
| Dark mode approach | Class-based (@custom-variant) |
Media query (prefers-color-scheme only) | Media query approach doesn't allow manual toggle. Users expect a toggle button. Class-based supports both: system preference as default, manual override via toggle. |
Installation
# Dev dependency only -- NOT bundled into the app
npm install -D @material/material-color-utilities
# That's it. No other new dependencies.
Files to Create
| File | Purpose |
|---|---|
scripts/generate-theme.ts |
Node script: seed color -> MD3 CSS tokens |
src/theme-tokens.css |
Generated output: CSS custom properties for light + dark |
src/hooks/useTheme.ts |
Theme toggle hook (light/dark/system) |
Files to Modify
| File | Change |
|---|---|
src/index.css |
Add @import "./theme-tokens.css", @custom-variant dark, @theme block |
package.json |
Add script: "generate-theme": "tsx scripts/generate-theme.ts" |
What NOT to Add
| Technology | Why Not |
|---|---|
| Material Tailwind / MUI / any component library | Rewrite cost exceeds benefit. Keep semantic HTML + Tailwind utilities with MD3 tokens. |
| CSS-in-JS (Emotion, styled-components) | Conflicts with Tailwind. Wrong direction. |
| next-themes | Next.js-specific. Vite SPA needs 15 lines of code, not a library. |
| framer-motion | MD3 motion is subtle transitions (opacity, scale). CSS transitions + Tailwind's @theme animation tokens handle it. |
| PostCSS plugins | Tailwind v4 uses the @tailwindcss/vite plugin, not PostCSS. Don't add PostCSS config. |
| tailwind.config.js | Tailwind v4 is CSS-first. All config goes in src/index.css via @theme. No JS config file. |
| Runtime color generation in browser | Pre-generate tokens at build time. Don't ship the color algorithm to users. |
Confidence Assessment
| Decision | Confidence | Basis |
|---|---|---|
| Zero new runtime dependencies | HIGH | Existing Tailwind v4 handles everything; verified in official docs |
@custom-variant dark for dark mode |
HIGH | Verified in official Tailwind v4 dark mode documentation |
@theme directive for MD3 tokens |
HIGH | Verified in official Tailwind v4 theme documentation |
@material/material-color-utilities 0.4.0 for token generation |
HIGH | Official Google package, actively maintained, used by Material Theme Builder |
| MD3 shape scale values (4/8/12/16/28/9999 px) | HIGH | Confirmed in official Material Design 3 shape documentation |
| MD3 elevation box-shadow values | MEDIUM | Values sourced from community reference (Studio N Creations) cross-referenced with Material Web component source. Official docs don't publish exact CSS box-shadow -- they use --md-elevation-level in their web components. The shadow values are a reasonable approximation. |
| No component library needed | HIGH | Project has 159 tests against current DOM structure; rewriting components is unjustified for a styling overhaul |
| Pre-generated accent colors (not runtime) | MEDIUM | Architectural choice -- runtime generation is valid but adds bundle weight for a rarely-used feature |
Sources
- Tailwind v4 Dark Mode Documentation --
@custom-variantsyntax, class-based toggle, localStorage pattern - Tailwind v4 Theme Documentation --
@themedirective, CSS variable generation, namespace conventions - Material Design 3 Design Tokens -- token naming, semantic color roles
- Material Design 3 Shape Scale -- corner radius values (4/8/12/16/28dp)
- Material Design 3 Elevation -- elevation levels 0-5
- @material/material-color-utilities on npm -- v0.4.0, official Google color algorithm
- Material Theme Builder -- CSS export format,
--md-sys-color-*naming convention - MD3 Box-Shadow CSS Values -- elevation shadow approximations (MEDIUM confidence)
- m3-tailwind-colors GitHub -- evaluated and rejected (3 stars, single maintainer)
- Tailwind v4 Multi-Theme Strategy -- community pattern for theme switching with CSS variables