Files

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:

  1. Require rewriting every existing component to match the library's API
  2. Break existing tests that assert on current DOM structure
  3. Add bundle weight for a wizard that needs at most 6-8 component types
  4. 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:

  1. Takes a seed color (hex)
  2. Uses themeFromSourceColor() to generate full MD3 palette (primary, secondary, tertiary, error, surface, outline, etc.)
  3. Outputs CSS custom properties in the --md-sys-color-* naming convention
  4. Writes to src/theme-tokens.css which is imported into src/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:

  1. Offer 3-5 preset seed colors (not arbitrary color picker)
  2. Pre-generate token sets for each seed color at build time
  3. 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