# Technology Stack **Project:** SimpleFinanceDash — UI Polish Milestone **Researched:** 2026-03-11 **Confidence:** HIGH (stack already locked; all versions confirmed from package.json and source files) --- ## Current Stack (Already Installed — Do Not Change) | Technology | Version | Role | |------------|---------|------| | React | 19.2.0 | UI runtime | | TypeScript | 5.9.3 | Type safety | | Vite | 7.3.1 | Build tool | | Tailwind CSS | 4.2.1 | Utility styling | | shadcn/ui | 4.0.0 | Component library | | radix-ui | 1.4.3 | Headless primitives (single package, not individual `@radix-ui/*`) | | Recharts | 2.15.4 | Charts | | tw-animate-css | 1.4.0 | CSS animation utilities | | Geist Variable | 5.2.8 | Primary font (already loaded, set as `--font-sans`) | | lucide-react | 0.577.0 | Icon set | **Critical context:** The project is on the new shadcn v4 architecture (single `radix-ui` package, `@theme inline` in CSS, `oklch` color space). All recommendations below are scoped to this exact setup. --- ## Recommended Stack — UI Polish Layer ### 1. CSS Variable Strategy for Pastel Theme **Technique: Replace oklch values in `:root` inside `index.css`** The current `index.css` uses default shadcn black/white tokens: - `--background: oklch(1 0 0)` → pure white - `--primary: oklch(0.205 0 0)` → near-black - `--secondary: oklch(0.97 0 0)` → near-white gray **Replace with pastel-tinted equivalents.** oklch is ideal for pastel work because the `C` (chroma) channel controls saturation independently of lightness. A pastel is high-L, low-C: ``` Soft lavender background: oklch(0.97 0.015 280) Soft blue primary: oklch(0.55 0.12 250) Rose secondary: oklch(0.94 0.025 350) Amber accent: oklch(0.94 0.04 85) Soft green: oklch(0.94 0.04 145) ``` **Finance category color tokens** (add as custom vars alongside shadcn defaults): ```css :root { /* Category semantic tokens — used in chart fills and row backgrounds */ --color-income: oklch(0.88 0.08 145); /* soft green */ --color-bill: oklch(0.88 0.07 250); /* soft blue */ --color-expense: oklch(0.90 0.08 85); /* soft amber */ --color-debt: oklch(0.90 0.08 15); /* soft red/rose */ --color-saving: oklch(0.88 0.07 280); /* soft violet */ --color-investment: oklch(0.90 0.07 320); /* soft pink */ } ``` Expose them in the `@theme inline` block so Tailwind generates utility classes (`bg-income`, `bg-bill`, etc.). **Confidence: HIGH** — This is how shadcn v4 + Tailwind v4 theming works. The `@theme inline` bridging pattern is confirmed by the existing codebase structure. --- ### 2. shadcn/ui Component Customization Strategy **Pattern: Variant extension via `class-variance-authority` (CVA) — already available** Do not fork shadcn component source files unless necessary. Instead: 1. **CSS variable changes** cover 80% of polish (background, border, primary, radius). 2. **className overrides at the call site** via `cn()` cover the remaining 20%. 3. For components that need a recurring custom style (e.g., Card with always-pastel header), wrap in a thin local component: `PastelCard`, `StatCard`. This avoids touching `ui/card.tsx`. **Radius token:** The current `--radius: 0.625rem` is fine. For a softer look, increase to `0.75rem` or `1rem`. **Border softness:** Replace `--border: oklch(0.922 0 0)` with a tinted, slightly lower-opacity token like `oklch(0.88 0.02 250 / 60%)` for a barely-there border that reads as "pastel spreadsheet cell divider." **Confidence: HIGH** --- ### 3. Chart Customization with Recharts + shadcn ChartContainer The codebase already has shadcn's `ChartContainer` / `ChartTooltipContent` wrapper in `ui/chart.tsx`. This is the correct foundation. **Current gap:** `ExpenseBreakdown.tsx` uses raw `` without `ChartContainer`. It should be migrated to use `ChartContainer` + `ChartConfig` so chart colors come from CSS variables rather than hardcoded hex strings. **Pattern to follow for all charts:** ```typescript const chartConfig = { bills: { label: 'Bills', color: 'var(--color-bill)' }, expenses: { label: 'Expenses', color: 'var(--color-expense)' }, savings: { label: 'Savings', color: 'var(--color-saving)' }, investments: { label: 'Investments', color: 'var(--color-investment)' }, } satisfies ChartConfig ``` Then use `fill="var(--color-bills)"` on Recharts `` elements. The `ChartStyle` component injects these as scoped CSS vars so dark mode works automatically. **Tooltip customization:** Use `` with a `formatter` prop to render currency-formatted values. The existing `ChartTooltipContent` already handles the indicator dot pattern — just pass `formatter={(value) => formatCurrency(value, currency)}`. **Recharts animation:** Recharts has built-in entrance animations on charts (`isAnimationActive={true}` is the default). The animation duration can be tuned via `animationDuration` (prop on ``, ``, ``). Set to `600`–`800ms` for a polished feel. Do NOT disable animations. **Chart types in use / recommended:** - `PieChart` — expense breakdown (already exists, needs ChartContainer migration) - `BarChart` — budget vs actual comparison (recommended addition for FinancialOverview) - `RadialBarChart` — AvailableBalance progress indicator (recommended to replace plain number display) **Confidence: HIGH** — Recharts 2.x API is stable and well-understood. The ChartContainer pattern is confirmed from the existing `ui/chart.tsx` source. --- ### 4. Animation and Transition Strategy **Primary tool: `tw-animate-css` (already installed)** `tw-animate-css` provides Tailwind v4-compatible utility classes wrapping Animate.css keyframes. Use it for: - Page entrance animations: `animate-fade-in` on dashboard sections - Dialog/sheet enters: `animate-in slide-in-from-bottom-4` (shadcn already applies these via `data-[state=open]` variants) - Loading skeletons: `animate-pulse` (Tailwind built-in, no extra library needed) **Do NOT add Motion/Framer Motion for this milestone.** Reason: the interactions needed (inline edit toggle, form submission feedback, card entrance) are all achievable with CSS transitions and `tw-animate-css`. Framer Motion adds ~45KB to the bundle and its React 19 compatibility had rough edges in early 2025. Revisit for a future milestone if complex drag-and-drop or physics animations are needed. **Inline edit transitions** (e.g., BillsTracker `InlineEditRow`): Use CSS `transition-all duration-150` on the toggle between display and input state. This is zero-dependency and already idiomatic in the codebase. **Recommended micro-interaction patterns:** - Input focus: `focus-visible:ring-2 focus-visible:ring-primary/40` for a soft glow - Row hover: `hover:bg-muted/60 transition-colors duration-150` - Button press: shadcn Button already has `active:scale-[0.98]` equivalent via its CVA variants — verify in `ui/button.tsx` - Number updates (balance changing): CSS `@keyframes` count-up is overkill; a simple `transition-opacity` flash on value change via a `key` prop reset is sufficient **Confidence: MEDIUM** — tw-animate-css is confirmed installed. Framer Motion React 19 compatibility claim is based on training data; the recommendation to skip it is defensive but well-justified on bundle size grounds alone. --- ### 5. Typography **Current state:** Geist Variable is already installed (`@fontsource-variable/geist`) and set as `--font-sans`. This is the correct font for this project. **Geist Variable characteristics:** Clean, geometric, excellent for data/numbers, neutral without being cold. Used by Vercel's dashboard products. Well-suited for finance UIs. **Do NOT add a second typeface.** The spreadsheet aesthetic is served by a single well-chosen sans. A display font pairing would clash with the data-dense layout. **Typography scale recommendations:** ```css /* Add to @theme inline */ --font-mono: 'Geist Mono Variable', ui-monospace, monospace; ``` Install `@fontsource-variable/geist-mono` alongside the existing font. Use `font-mono tabular-nums` on all currency amounts. The existing `ChartTooltipContent` already does this (`font-mono tabular-nums` class on values) — extend this pattern to table cells showing currency. **Text hierarchy for finance:** - Card titles: `text-sm font-semibold tracking-wide text-muted-foreground uppercase` — reads as a spreadsheet column header - Primary numbers (balance): `text-3xl font-bold tabular-nums` - Secondary numbers (line items): `text-sm tabular-nums` - Labels: `text-xs text-muted-foreground` **Confidence: HIGH** — Font is already installed. Geist Mono is the natural companion and from the same @fontsource-variable namespace. --- ### 6. Icon Usage (lucide-react) **Already installed at 0.577.0.** lucide-react is the correct choice — it's the shadcn default and provides consistent stroke-width icons that match the component style. **Pattern for finance icons:** | Concept | Lucide Icon | |---------|-------------| | Income | `TrendingUp` | | Bills | `Receipt` | | Variable expenses | `ShoppingCart` | | Debt | `CreditCard` | | Savings | `PiggyBank` | | Investments | `BarChart2` | | Settings | `Settings2` | | Categories | `Tag` | | Budget period | `CalendarDays` | Use `size={16}` consistently for inline icons, `size={20}` for standalone icon buttons. **Confidence: HIGH** --- ### 7. Layout Pattern for Dashboard The current `DashboardPage.tsx` uses a flat `flex flex-col gap-6 p-6` layout. For polish, the recommended evolution is: **Header area:** Sticky top bar with budget selector + month navigation + key stat (available balance). Removes the selector from inline flow. **Grid approach (keep existing):** The `lg:grid-cols-3` + `lg:col-span-2` pattern for FinancialOverview + AvailableBalance is correct. Continue using CSS Grid for two-column chart sections. **Card anatomy standard:** - `CardHeader`: Always has a soft background gradient using two adjacent pastel tokens - `CardTitle`: Uses the uppercase label style (see Typography section) - `CardContent`: `p-0` for tables, `pt-4` for charts/other content This pattern already exists in the codebase (BillsTracker, FinancialOverview headers use `bg-gradient-to-r from-blue-50 to-indigo-50`). The task is to make it consistent and driven by category-semantic tokens rather than hardcoded Tailwind color names. **Do NOT use Tailwind's `bg-blue-50` directly.** Reason: when the CSS variable values are updated for theming, hardcoded `bg-blue-50` won't change. Use `bg-(--color-bill)/20` syntax to reference semantic tokens. **Confidence: HIGH** --- ## Alternatives Considered | Category | Recommended | Alternative | Why Not | |----------|-------------|-------------|---------| | Animation | tw-animate-css (installed) | Framer Motion | Bundle cost (~45KB), React 19 edge cases, overkill for this scope | | Animation | tw-animate-css (installed) | react-spring | Same — overkill, no benefit over CSS transitions for this UI | | Charts | Recharts (installed) | Victory / Nivo | Recharts is already embedded, ChartContainer wrapper is done, switching has zero upside | | Charts | Recharts (installed) | D3 directly | Too low-level; maintenance cost far exceeds polish benefit | | Fonts | Geist Variable (installed) | Inter | Geist is already there; Inter would be a downgrade in distinctiveness | | Fonts | Single font | Font pairing | Finance dashboards read better with single-family discipline | | Color format | oklch (current) | HSL / HEX | oklch is perceptually uniform, better for generating harmonious pastel palettes | --- ## What NOT to Use **Do NOT install:** - `framer-motion` — React 19 had compatibility issues in early 2025; bundle cost not justified - `@radix-ui/*` individual packages — The project is on the new `radix-ui` unified package; mixing them causes version conflicts - `react-select` / custom dropdown libraries — shadcn's `Select` covers the need - A CSS-in-JS solution — Tailwind v4 + CSS variables is the correct pattern; CSS-in-JS would fight the build setup - A color picker library — custom themes are explicitly out of scope - `date-fns` or `dayjs` — only needed if date formatting becomes complex; standard `Intl.DateTimeFormat` is sufficient - `react-hook-form` — the forms in scope (login, budget creation) are simple enough for controlled inputs; adding a form library is premature --- ## Key Tailwind v4 Syntax Notes **Important: Tailwind v4 changed syntax.** The codebase correctly uses the new forms but contributors must be aware: | v3 syntax | v4 syntax | |-----------|-----------| | `bg-opacity-50` | `bg-black/50` or `oklch(.../50%)` | | `text-opacity-75` | `text-foreground/75` | | `bg-[--color-bill]` | `bg-(--color-bill)` (parentheses, not brackets) | | `@apply border-border` | Same (works in v4) | | Plugin-based config | `@theme inline` in CSS | Use `bg-(--color-bill)/20` to reference a CSS variable with opacity. This is the correct v4 syntax. **Confidence: HIGH** — Confirmed by reading the existing `index.css` which uses the `@theme inline` pattern correctly. --- ## Installation No new packages are required for the core polish milestone. All tools are already installed. **Optional — only if currency formatting needs locale support beyond what exists:** ```bash # Nothing to add — Intl.NumberFormat is native ``` **If Geist Mono is desired (recommended for tabular numbers):** ```bash cd frontend && bun add @fontsource-variable/geist-mono ``` Then in `index.css`: ```css @import "@fontsource-variable/geist-mono"; ``` And in `@theme inline`: ```css --font-mono: 'Geist Mono Variable', ui-monospace, monospace; ``` --- ## Sources - Confirmed from codebase: `/frontend/package.json`, `/frontend/src/index.css`, `/frontend/src/components/ui/chart.tsx` - shadcn v4 `@theme inline` + `oklch` color pattern: confirmed from existing `index.css` structure (HIGH confidence) - Tailwind v4 CSS variable reference syntax (`bg-(--var)`): confirmed from Tailwind v4 docs in training data, consistent with codebase patterns (HIGH confidence) - Recharts ChartContainer pattern: confirmed from existing `ui/chart.tsx` implementation (HIGH confidence) - tw-animate-css v4 compatibility: confirmed from import in `index.css` (`@import "tw-animate-css"`) (HIGH confidence) - Framer Motion React 19 compatibility concerns: training data (MEDIUM confidence — may have improved in later 2025 patch releases, but avoidance recommendation stands on bundle-size grounds alone)