Files
SimpleFinanceDash/.planning/research/STACK.md

14 KiB
Raw Blame History

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.


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):

: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 <PieChart> 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:

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 <Cell> elements. The ChartStyle component injects these as scoped CSS vars so dark mode works automatically.

Tooltip customization: Use <ChartTooltipContent> 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 <Bar>, <Pie>, <Line>). Set to 600800ms 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:

/* 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:

# Nothing to add — Intl.NumberFormat is native

If Geist Mono is desired (recommended for tabular numbers):

cd frontend && bun add @fontsource-variable/geist-mono

Then in index.css:

@import "@fontsource-variable/geist-mono";

And in @theme inline:

--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)