--- phase: 7 slug: setup-wizard status: draft shadcn_initialized: true preset: new-york created: 2026-04-20 --- # Phase 7 — UI Design Contract > Visual and interaction contract for the 3-step setup wizard. Generated by gsd-ui-researcher, verified by gsd-ui-checker. --- ## Design System | Property | Value | |----------|-------| | Tool | shadcn (new-york style) | | Preset | new-york, baseColor neutral, cssVariables true | | Component library | Radix UI (via shadcn/ui) | | Icon library | Lucide | | Font | Inter, ui-sans-serif, system-ui, sans-serif | --- ## Spacing Scale Declared values (must be multiples of 4): | Token | Value | Usage | |-------|-------|-------| | xs | 4px | Icon gaps, inline padding | | sm | 8px | Compact element spacing, checkbox-to-label gap | | md | 16px | Default element spacing, card internal padding | | lg | 24px | Section padding, stepper step gaps | | xl | 32px | Layout gaps, card content vertical spacing | | 2xl | 48px | Step card top/bottom padding | | 3xl | 64px | Page-level vertical centering offset | Exceptions: Sticky allocation bar uses 12px vertical padding (py-3) for visual compactness within the card. --- ## Typography | Role | Size | Weight | Line Height | |------|------|--------|-------------| | Body | 14px | 400 (regular) | 1.5 | | Label | 14px | 600 (semibold) | 1.4 | | Heading | 20px | 600 (semibold) | 1.2 | | Display | 28px | 600 (semibold) | 1.2 | Only two weights are used: 400 (regular) for body text and 600 (semibold) for labels, headings, and display. No medium (500) weight exists in this contract. Usage in wizard: - **Display (28px/600):** Wizard page title "Set up your budget" (step 1 heading area) - **Heading (20px/600):** Step card titles ("Monthly Income", "Recurring Items", "Review") - **Label (14px/600):** Category group headers (e.g., "Bills", "Variable Expenses"), stepper step labels, "Remaining to allocate" label, form field labels - **Body (14px/400):** Preset item names, amounts, helper text, step descriptions --- ## Color | Role | Value | Usage | |------|-------|-------| | Dominant (60%) | `oklch(0.98 0.01 260)` (--background) | Page background behind wizard card | | Secondary (30%) | `oklch(1 0 0)` (--card) | Wizard card surface, review summary card | | Accent (10%) | `oklch(0.55 0.15 260)` (--primary) | Active stepper step indicator, primary CTA buttons, step number circles (active) | | Destructive | `oklch(0.6 0.2 25)` (--destructive) | Negative "remaining to allocate" text, validation error text | **Focal point:** The primary CTA button ("Next Step" / "Complete Setup") in the bottom-right of the card is the single focal point on every step. It is the only large accent-colored element in the viewport, drawing the eye to the next action. Accent reserved for: - Active step circle in the stepper (filled primary background) - "Next Step" and "Complete Setup" primary buttons - Income input focus ring - Completed step checkmark icon color Additional semantic colors used (existing tokens): - `--color-income` (`oklch(0.55 0.17 155)`): Income category group header dot and badge - `--color-bill` (`oklch(0.55 0.17 25)`): Bill category group header dot and badge - `--color-variable-expense` (`oklch(0.58 0.16 50)`): Variable expense category group header dot and badge - `--color-debt` (`oklch(0.52 0.18 355)`): Debt category group header dot and badge - `--color-saving` (`oklch(0.55 0.16 220)`): Saving category group header dot and badge - `--color-investment` (`oklch(0.55 0.16 285)`): Investment category group header dot and badge - `--color-on-budget` (`oklch(0.50 0.17 155)`): Positive "remaining to allocate" text (green) --- ## Component Inventory ### New shadcn Components to Install | Component | Purpose | |-----------|---------| | `checkbox` | Preset item selection in step 2 | ### Existing Components Used | Component | Where | |-----------|-------| | `Card`, `CardHeader`, `CardContent` | Wizard step container, review summary | | `Button` | Next Step, Go Back, Skip Step, Skip setup, Complete Setup | | `Input` | Income amount (step 1), per-item amount editing (step 2) | | `Badge` | Category type indicators next to preset items | | `Label` | Form field labels | | `Separator` | Between category groups in step 2 and review step | ### Custom Components to Build | Component | Description | |-----------|-------------| | `SetupWizard` | Page-level orchestrator: holds wizard state, renders stepper + active step | | `WizardStepper` | Horizontal numbered stepper bar (1-2-3) with clickable completed steps | | `IncomeStep` | Step 1: single income input with currency indicator | | `RecurringItemsStep` | Step 2: grouped checklist of 19 presets with editable amounts | | `ReviewStep` | Step 3: read-only summary of selections | | `AllocationBar` | Sticky bar showing "Remaining to allocate" with live calculation | | `PresetItemRow` | Single checklist row: checkbox + name + category badge + amount input | | `CategoryGroupHeader` | Section header with colored dot + category type label + item count | --- ## Layout Contract ### Page Shell ``` Full viewport height, centered content: - Container: flex min-h-screen items-center justify-center bg-background p-4 - No sidebar, no app nav — standalone page like login/register - Content wrapper: w-full max-w-2xl (672px) with vertical flex layout ``` ### Stepper Bar ``` Position: Above the card, inside the content wrapper Layout: flex items-center justify-center gap-8 Each step: - Circle: 32px (w-8 h-8), centered number text (14px/600) - Active: bg-primary text-primary-foreground - Completed: bg-primary text-primary-foreground with Check icon (16px) - Upcoming: bg-muted text-muted-foreground border border-border - Connector line between steps: h-px w-16 bg-border (completed: bg-primary) - Step label below circle: text-xs font-semibold text-muted-foreground (active: text-foreground) Clickable: Completed steps and current step only (not future steps) Margin below stepper: 24px (mb-6) before the card ``` ### Step Card ``` Card: w-full border border-border shadow-sm CardHeader: pb-2 - Step title: heading (20px/600) - Step description: body (14px/400) text-muted-foreground, 1 line CardContent: space-y-6 Bottom navigation row (inside CardContent, at the bottom): - flex justify-between items-center pt-4 border-t border-border - Left: Go Back button (variant="ghost", hidden on step 1) + Skip Step button (variant="ghost", text-muted-foreground) - Right: Next Step button (variant="default", primary) or Complete Setup button (step 3) ``` ### Step 1 — Income ``` Card content: - Label: "Monthly net income" (14px/600) - Input row: flex items-center gap-2 - Input: type="number", w-full, text-right, text-lg (18px), pre-filled "3000" - Currency suffix: text-muted-foreground text-sm, shows profile currency (e.g., "EUR") - Helper text below: "Enter your total monthly take-home pay" (14px/400, text-muted-foreground) - Validation: if empty or <= 0 on Next Step click, show destructive text below input: "Please enter a positive income amount" ``` ### Step 2 — Recurring Items ``` Allocation bar (sticky): - Position: sticky top-0 z-10, inside the card content area - Layout: flex justify-between items-center py-3 px-4 bg-muted border-b border-border - Left: "Remaining to allocate" (14px/600) - Right: formatted amount (16px/600) - Positive or zero: text-on-budget (green) - Negative: text-destructive (red) - Updates live on every check/uncheck and amount edit Category groups (6 groups, ordered: income, bill, variable_expense, debt, saving, investment): - CategoryGroupHeader: flex items-center gap-2 pt-4 pb-2 - Colored dot: w-2.5 h-2.5 (10px) circle using category color token - Group label: label (14px/600) using categoryLabels from palette.ts - Item count badge: text-xs text-muted-foreground "(4 items)" - Separator below header: border-border PresetItemRow (per item): - Layout: flex items-center gap-3 py-2.5 px-1 - Checkbox: shadcn checkbox (16px square, sharp corners via radius-0) - Item name: body (14px/400), flex-1 - Uses i18n key: t(`presets.${type}.${slug}`) - Category badge: Badge variant="outline", text-xs, styled with category color border-left (3px) - Amount input: w-24 text-right, type="number" - Enabled only when checkbox is checked - Disabled state: bg-muted text-muted-foreground opacity-50 - Pre-filled with preset defaultAmount Default checked state: - bill (4 items): ALL checked - variable_expense (5 items): ALL checked - income, debt, saving, investment: ALL unchecked ``` ### Step 3 — Review ``` Card content: - Read-only summary, no editable fields - Income row: flex justify-between py-2 - "Monthly income" (14px/600) - Formatted amount (14px/600) - Separator - Selected items grouped by category type (same order as step 2) - CategoryGroupHeader (same component, no checkbox) - Per item: flex justify-between py-1.5 px-1 - Item name (14px/400) - Amount (14px/400, text-right) - Separator - Totals section: space-y-1 pt-2 - "Total expenses" row: flex justify-between, 14px/600 - "Remaining" row: flex justify-between, 16px/600 - Positive: text-on-budget - Negative: text-destructive - If no items selected: show muted message "No items selected. You can add items to your template later." ``` ### Skip Controls ``` Per-step skip: Ghost button "Skip Step" in the bottom-left nav area - Advances to next step without saving current step data - On step 3 skip: same as "Skip setup" (exits wizard) Global skip: "Skip setup" link/button - Position: below the card, centered, text-sm text-muted-foreground underline - Margin top: 16px below card - Action: clears localStorage, sets profiles.setup_completed = true, redirects to dashboard - No confirmation dialog (non-destructive — user can still manually set up template) ``` --- ## Interaction Contract ### Step Navigation | Action | Behavior | |--------|----------| | Click "Next Step" (step 1) | Validate income > 0. If valid, transition to step 2. If invalid, show inline error. | | Click "Next Step" (step 2) | No validation required (0 items selected is valid). Transition to step 3. | | Click "Go Back" | Return to previous step. Preserve all entered data. | | Click stepper circle (completed) | Navigate to that step. Preserve all data. | | Click stepper circle (future) | No action. Cursor: default. | | Click "Complete Setup" (step 3) | Create categories + template items via API. On success: clear localStorage, set setup_completed=true, redirect to dashboard with toast. | | Click "Skip Step" (per-step) | Advance to next step without current step data. Step 1 skip: income stays at default or last entered value. Step 2 skip: uncheck all items. | | Click "Skip setup" (global) | Exit wizard entirely. Clear localStorage. Mark setup_completed=true. Redirect to dashboard. No toast. | | Page refresh mid-wizard | Restore wizard at the same step with all entered data from localStorage. | ### State Persistence (localStorage) ``` Key: `setup-wizard-${userId}` Value: JSON object { currentStep: 1 | 2 | 3, income: number, selectedItems: Record, // keyed by preset slug } Cleared on: wizard completion OR skip setup ``` ### Loading & Error States | State | Behavior | |-------|----------| | Wizard loading (useFirstRunState pending) | Show centered Skeleton matching card dimensions (max-w-2xl, h-64) | | Completion API in progress | "Complete Setup" button shows spinner + disabled. Go Back/Skip Step also disabled. | | Completion API failure | Toast (sonner, variant destructive): "Could not save your template. Please try again." Button re-enables. Data preserved. | | Partial completion failure | If categories created but template items fail: toast with "Some items could not be saved. Check your template page." Redirect to dashboard anyway. | ### Transitions Between Steps No animated transitions between steps. Instant swap of step content within the card. The stepper bar updates synchronously. --- ## Copywriting Contract All copy must have i18n keys in both `en.json` and `de.json`. Keys live under a `setup` namespace. | Element | EN Copy | i18n Key | |---------|---------|----------| | Page title (step 1) | Set up your budget | `setup.title` | | Step 1 title | Monthly Income | `setup.step1.title` | | Step 1 description | How much do you earn each month? | `setup.step1.description` | | Step 1 label | Monthly net income | `setup.step1.incomeLabel` | | Step 1 helper | Enter your total monthly take-home pay | `setup.step1.helper` | | Step 1 validation | Please enter a positive income amount | `setup.step1.validation` | | Step 2 title | Recurring Items | `setup.step2.title` | | Step 2 description | Select your regular monthly expenses | `setup.step2.description` | | Allocation bar label | Remaining to allocate | `setup.step2.remaining` | | Step 3 title | Review | `setup.step3.title` | | Step 3 description | Confirm your budget template | `setup.step3.description` | | Step 3 income label | Monthly income | `setup.step3.incomeLabel` | | Step 3 total label | Total expenses | `setup.step3.totalLabel` | | Step 3 remaining label | Remaining | `setup.step3.remainingLabel` | | Step 3 empty | No items selected. You can add items to your template later. | `setup.step3.empty` | | Stepper labels | Income / Items / Review | `setup.steps.1` / `setup.steps.2` / `setup.steps.3` | | Next button | Next Step | `setup.next` | | Back button | Go Back | `setup.back` | | Skip button | Skip Step | `setup.skip` | | Skip setup link | Skip setup | `setup.skipSetup` | | Complete button | Complete Setup | `setup.complete` | | Success toast | Template created! Your first budget will appear automatically. | `setup.toast.success` | | Error toast | Could not save your template. Please try again. | `setup.toast.error` | | Partial error toast | Some items could not be saved. Check your template page. | `setup.toast.partialError` | --- ## Registry Safety | Registry | Blocks Used | Safety Gate | |----------|-------------|-------------| | shadcn official | checkbox | not required | No third-party registries declared. --- ## Accessibility Contract | Concern | Implementation | |---------|----------------| | Stepper semantics | `role="navigation"` with `aria-label="Setup progress"`. Each step: `role="tab"`, `aria-selected` for active, `aria-disabled` for future. | | Step content | `role="tabpanel"` with `aria-labelledby` pointing to the active step tab. | | Checkbox group | Each category group is a `fieldset` with `legend` (visually styled as CategoryGroupHeader). | | Income input | `aria-describedby` pointing to helper text and validation error (when shown). | | Allocation bar | `aria-live="polite"` so screen readers announce remaining amount changes. | | Skip links | Visible, keyboard-focusable. Not hidden behind hover. | | Focus management | On step transition, focus moves to the step card heading. | --- ## Responsive Behavior | Breakpoint | Behavior | |------------|----------| | >= 768px (md) | max-w-2xl card centered, stepper horizontal with labels below circles | | < 768px (sm) | Card becomes full-width (mx-4). Stepper collapses: show circles only, hide step labels. Amount inputs remain w-24. | | < 480px (xs) | PresetItemRow: badge hidden, amount input w-20. Category group headers wrap naturally. | --- ## Checker Sign-Off - [ ] Dimension 1 Copywriting: PASS - [ ] Dimension 2 Visuals: PASS - [ ] Dimension 3 Color: PASS - [ ] Dimension 4 Typography: PASS - [ ] Dimension 5 Spacing: PASS - [ ] Dimension 6 Registry Safety: PASS **Approval:** pending