Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
271 lines
15 KiB
Markdown
271 lines
15 KiB
Markdown
# 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:
|
|
|
|
```css
|
|
/* 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
|
|
|
|
```css
|
|
/* 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:
|
|
```tsx
|
|
<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:
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```bash
|
|
# 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](https://tailwindcss.com/docs/dark-mode) -- `@custom-variant` syntax, class-based toggle, localStorage pattern
|
|
- [Tailwind v4 Theme Documentation](https://tailwindcss.com/docs/theme) -- `@theme` directive, CSS variable generation, namespace conventions
|
|
- [Material Design 3 Design Tokens](https://m3.material.io/foundations/design-tokens) -- token naming, semantic color roles
|
|
- [Material Design 3 Shape Scale](https://m3.material.io/styles/shape/corner-radius-scale) -- corner radius values (4/8/12/16/28dp)
|
|
- [Material Design 3 Elevation](https://m3.material.io/styles/elevation/applying-elevation) -- elevation levels 0-5
|
|
- [@material/material-color-utilities on npm](https://www.npmjs.com/package/@material/material-color-utilities) -- v0.4.0, official Google color algorithm
|
|
- [Material Theme Builder](https://material-foundation.github.io/material-theme-builder/) -- CSS export format, `--md-sys-color-*` naming convention
|
|
- [MD3 Box-Shadow CSS Values](https://studioncreations.com/blog/material-design-3-box-shadow-css-values/) -- elevation shadow approximations (MEDIUM confidence)
|
|
- [m3-tailwind-colors GitHub](https://github.com/somteacodes/m3-tailwind-colors) -- evaluated and rejected (3 stars, single maintainer)
|
|
- [Tailwind v4 Multi-Theme Strategy](https://simonswiss.com/posts/tailwind-v4-multi-theme) -- community pattern for theme switching with CSS variables
|