Personal Finance Budget
blockfinanceYNAB and Copilot-style zero-based monthly budget planner: envelope category budgeting, monthly pacing metrics, real-time spending progress by envelope, color-coded health indicators, and savings goal trackers.
Installation
$pnpm dlx uipkge-ng@latest add personal-finance-budget$npx uipkge-ng@latest add personal-finance-budget$yarn dlx uipkge-ng@latest add personal-finance-budget$bunx uipkge-ng@latest add personal-finance-budgetManifest:
https://uipkge.dev/r/angular/personal-finance-budget.jsonInstalls to:src/app/components/blocks/personal-finance-budget/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
EnvelopeCategoryinterface EnvelopeCategory {
id: string
name: string
icon: unknown
budgeted: number
spent: number
note: string
status: EnvelopeStatus
statusLabel: string
isSavings?: boolean
}EnvelopeSectioninterface EnvelopeSection {
id: string
title: string
description: string
icon: unknown
totalBudgeted: number
totalSpent: number
categories: EnvelopeCategory[]
}Files installed (2)
src/app/components/blocks/personal-finance-budget/personal-finance-budget.component.ts21.9 kB
import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { Calendar, CheckCircle2, Clock, CreditCard, Film, FolderPlus, HeartPulse, Home, Laptop, LucideAngularModule, PiggyBank, Plane, Plus, ShieldCheck, ShoppingCart, TrendingUp, UtensilsCrossed, Wallet, Zap, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiProgressComponent } from '@/ui/progress/progress.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' type EnvelopeStatus = 'healthy' | 'warning' | 'completed' | 'on-track' interface EnvelopeCategory { id: string name: string icon: unknown budgeted: number spent: number note: string status: EnvelopeStatus statusLabel: string isSavings?: boolean } interface EnvelopeSection { id: string title: string description: string icon: unknown totalBudgeted: number totalSpent: number categories: EnvelopeCategory[] } const sections: EnvelopeSection[] = [ { id: 'immediate-obligations', title: 'Immediate Obligations', description: 'Non-negotiable fixed living expenses and essential monthly bills.', icon: Home, totalBudgeted: 3750, totalSpent: 3550, categories: [ { id: 'cat-housing', name: 'Housing & Rent', icon: Home, budgeted: 2400, spent: 2400, note: 'Mortgage, HOA fee & insurance · Auto-paid Aug 1', status: 'completed', statusLabel: 'Paid in Full', }, { id: 'cat-utilities', name: 'Utilities & Internet', icon: Zap, budgeted: 350, spent: 320, note: 'Electric ($185), Water ($75), Fiber ($60)', status: 'healthy', statusLabel: 'Healthy', }, { id: 'cat-groceries', name: 'Groceries & Household', icon: ShoppingCart, budgeted: 800, spent: 650, note: 'Trader Joe’s & weekly produce · $15.00/day left', status: 'healthy', statusLabel: 'Healthy', }, { id: 'cat-health', name: 'Health & Medical', icon: HeartPulse, budgeted: 200, spent: 180, note: 'Prescription copays & health membership', status: 'healthy', statusLabel: 'Healthy', }, ], }, { id: 'lifestyle-discretionary', title: 'Lifestyle & Discretionary', description: 'Variable lifestyle choices, dining, entertainment, and digital subscriptions.', icon: UtensilsCrossed, totalBudgeted: 700, totalSpent: 625, categories: [ { id: 'cat-dining', name: 'Dining Out & Cafes', icon: UtensilsCrossed, budgeted: 400, spent: 380, note: 'Restaurants, coffee & takeout · $20.00 left for 10 days', status: 'warning', statusLabel: 'Near Limit', }, { id: 'cat-entertainment', name: 'Entertainment & Leisure', icon: Film, budgeted: 200, spent: 150, note: 'Cinema tickets, gaming pass & weekend outings', status: 'healthy', statusLabel: 'Healthy', }, { id: 'cat-tech', name: 'Tech & Subscriptions', icon: Laptop, budgeted: 100, spent: 95, note: 'iCloud, Spotify, GitHub Pro, ChatGPT Plus', status: 'warning', statusLabel: 'Near Limit', }, ], }, { id: 'savings-investments', title: 'Savings Goals & Investments', description: 'Dedicated wealth accumulation, emergency reserve, and planned sinking funds.', icon: PiggyBank, totalBudgeted: 1583, totalSpent: 1483, categories: [ { id: 'cat-emergency', name: 'Emergency Fund', icon: ShieldCheck, budgeted: 500, spent: 500, note: 'High-yield cash reserve · Total balance: $18,500.00', status: 'completed', statusLabel: 'Goal Met', isSavings: true, }, { id: 'cat-roth', name: 'Roth IRA Contribution', icon: TrendingUp, budgeted: 583, spent: 583, note: 'Broad-market index ETF · 2026 cap pacing ($7,000/yr)', status: 'completed', statusLabel: 'Maxed Monthly', isSavings: true, }, { id: 'cat-vacation', name: 'Vacation & Travel Fund', icon: Plane, budgeted: 500, spent: 400, note: 'Tokyo autumn trip 2027 · $3,400.00 of $5,000.00 goal', status: 'on-track', statusLabel: '80% Funded', isSavings: true, }, ], }, ] function formatCurrency(amount: number): string { return `$${amount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` } /** * PersonalFinanceBudget -- the React block, 1:1: zero-based monthly budget header with * assignment pill, four overview cards (income, budgeted/spent, savings, month pace), * envelope category tables grouped by section with progress and health badges, and a * zero-based allocation philosophy footer. */ @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-personal-finance-budget, [ui-personal-finance-budget]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiProgressComponent, UiTableComponent, UiTableBodyComponent, UiTableCellComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <div data-slot="personal-finance-budget" [class]="rootClass"> <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between"> <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2.5"> <h1 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl">Monthly Zero-Based Budget</h1> <div class="bg-muted text-foreground border-border flex items-center gap-1 rounded-md border px-2 py-0.5 text-xs font-semibold" > <lucide-icon [img]="Calendar" class="text-muted-foreground size-3.5" /> <span>August 2026</span> </div> </div> <div class="flex flex-wrap items-center gap-2 pt-0.5"> <div class="border-success/20 bg-success/10 text-success inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium" > <lucide-icon [img]="CheckCircle2" class="size-3.5 shrink-0" /> <span >To be Budgeted: <strong class="font-semibold tabular-nums">$0.00</strong> · Every dollar assigned!</span > </div> <span class="text-muted-foreground hidden text-xs sm:inline">Zero-Based Envelope Method</span> </div> </div> <div class="flex items-center gap-2"> <button ui-button variant="outline" size="sm" class="gap-1.5 shadow-xs"> <lucide-icon [img]="FolderPlus" class="text-muted-foreground size-4" /> <span>Add Category</span> </button> <button ui-button size="sm" class="gap-1.5 shadow-xs"> <lucide-icon [img]="Plus" class="size-4" /> <span>Add Expense</span> </button> </div> </div> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <div ui-card class="flex flex-col justify-between shadow-xs"> <div ui-card-header class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex items-center gap-2"> <div aria-hidden="true" class="border-success/20 bg-success/10 text-success flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="Wallet" class="size-4" /> </div> <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Total Monthly Income</h3> </div> <span ui-badge variant="success" class="text-xs">100% Assigned</span> </div> </div> <div ui-card-content class="space-y-2"> <div> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">$8,500.00</div> <p class="text-muted-foreground mt-0.5 text-xs">2 Inflow streams (Salary & Freelance)</p> </div> <div class="border-border/60 text-muted-foreground flex items-center justify-between border-t pt-2 text-xs"> <span>Unassigned Pool</span> <span class="text-success font-semibold tabular-nums">$0.00</span> </div> </div> </div> <div ui-card class="flex flex-col justify-between shadow-xs"> <div ui-card-header class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex items-center gap-2"> <div aria-hidden="true" class="bg-muted text-muted-foreground border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="CreditCard" class="size-4" /> </div> <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Budgeted & Spent</h3> </div> <span ui-badge variant="warning" class="text-xs tabular-nums">80.5% Spent</span> </div> </div> <div ui-card-content class="space-y-2"> <div> <div class="flex items-baseline gap-1 text-2xl font-bold tracking-tight"> <span class="text-foreground tabular-nums">$6,840.00</span> <span class="text-muted-foreground text-xs font-normal tabular-nums">/ $8,500.00</span> </div> <div class="mt-2 space-y-1"> <ui-progress [value]="80.5" class="[&_[data-slot=progress-indicator]]:bg-warning h-2" /> </div> </div> <div class="border-border/60 text-muted-foreground flex items-center justify-between border-t pt-2 text-xs"> <span>Remaining in Envelopes</span> <span class="text-foreground font-medium tabular-nums">$1,660.00</span> </div> </div> </div> <div ui-card class="flex flex-col justify-between shadow-xs"> <div ui-card-header class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex items-center gap-2"> <div aria-hidden="true" class="border-success/20 bg-success/10 text-success flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="PiggyBank" class="size-4" /> </div> <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Savings Allocated</h3> </div> <span ui-badge variant="success" class="text-xs tabular-nums">19.5% Rate</span> </div> </div> <div ui-card-content class="space-y-2"> <div> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">$1,660.00</div> <div class="mt-2 space-y-1"> <ui-progress [value]="89.3" class="[&_[data-slot=progress-indicator]]:bg-success h-2" /> </div> </div> <div class="border-border/60 text-muted-foreground flex items-center justify-between border-t pt-2 text-xs"> <span>Funded to Date</span> <span class="text-success font-semibold tabular-nums">$1,483.00 (89.3%)</span> </div> </div> </div> <div ui-card class="flex flex-col justify-between shadow-xs"> <div ui-card-header class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex items-center gap-2"> <div aria-hidden="true" class="bg-muted text-muted-foreground border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="Clock" class="size-4" /> </div> <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Month Pace</h3> </div> <span ui-badge variant="outline" class="text-xs font-normal tabular-nums">Day 21 of 31</span> </div> </div> <div ui-card-content class="space-y-2"> <div> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">10 Days left</div> <div class="mt-2 space-y-1"> <ui-progress [value]="67.7" class="h-2" /> </div> </div> <div class="border-border/60 text-muted-foreground flex items-center justify-between border-t pt-2 text-xs"> <span>Cycle Elapsed</span> <span class="text-foreground font-medium tabular-nums">67.7% of month</span> </div> </div> </div> </div> <div class="space-y-6"> @for (section of sections; track section.id) { <div ui-card class="overflow-hidden shadow-xs"> <div ui-card-header class="bg-muted/20 border-border border-b py-4"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-0.5"> <div class="flex items-center gap-2"> <lucide-icon [img]="asIcon(section.icon)" class="text-muted-foreground size-4 shrink-0" /> <h3 ui-card-title class="text-base font-semibold">{{ section.title }}</h3> </div> <p ui-card-description class="text-xs">{{ section.description }}</p> </div> <div class="flex items-center gap-3 text-xs"> <div class="bg-background border-border flex items-center gap-2 rounded-md border px-3 py-1.5 shadow-2xs" > <span class="text-muted-foreground">Subtotal:</span> <span class="text-foreground font-semibold tabular-nums">{{ format(section.totalSpent) }}</span> <span class="text-muted-foreground">/</span> <span class="text-muted-foreground font-medium tabular-nums">{{ format(section.totalBudgeted) }}</span> </div> </div> </div> </div> <div ui-card-content class="p-0"> <div class="overflow-x-auto"> <table ui-table> <thead ui-table-header> <tr ui-table-row class="hover:bg-transparent"> <th ui-table-head class="min-w-[240px] text-xs font-semibold">Envelope Category</th> <th ui-table-head class="text-right text-xs font-semibold"> {{ section.id === 'savings-investments' ? 'Monthly Target' : 'Budgeted' }} </th> <th ui-table-head class="text-right text-xs font-semibold"> {{ section.id === 'savings-investments' ? 'Funded to Date' : 'Spent to Date' }} </th> <th ui-table-head class="text-right text-xs font-semibold"> {{ section.id === 'savings-investments' ? 'Remaining to Goal' : 'Available Balance' }} </th> <th ui-table-head class="min-w-[180px] text-xs font-semibold">Progress & Pace</th> <th ui-table-head class="w-[110px] text-right text-xs font-semibold">Status</th> </tr> </thead> <tbody ui-table-body> @for (cat of section.categories; track cat.id) { <tr ui-table-row class="hover:bg-muted/40 transition-colors"> <td ui-table-cell class="py-3.5"> <div class="flex items-start gap-3"> <div aria-hidden="true" class="bg-muted text-muted-foreground border-border mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="asIcon(cat.icon)" class="size-4" /> </div> <div class="space-y-0.5"> <p class="text-foreground text-xs font-semibold">{{ cat.name }}</p> <p class="text-muted-foreground text-xs">{{ cat.note }}</p> </div> </div> </td> <td ui-table-cell class="text-foreground py-3.5 text-right text-xs font-medium tabular-nums"> {{ format(cat.budgeted) }} </td> <td ui-table-cell class="text-foreground py-3.5 text-right text-xs font-medium tabular-nums"> {{ format(cat.spent) }} </td> <td ui-table-cell class="py-3.5 text-right text-xs"> <span [class]="remainingClass(cat)">{{ format(cat.budgeted - cat.spent) }}</span> </td> <td ui-table-cell class="py-3.5"> <div class="space-y-1.5"> <div class="flex items-center justify-between text-xs"> <span class="text-muted-foreground tabular-nums"> {{ percent(cat) }}% {{ cat.isSavings ? 'funded' : 'used' }} </span> <span class="text-muted-foreground text-xs tabular-nums">{{ format(cat.spent) }}</span> </div> <ui-progress [value]="percent(cat)" [class]="progressClass(cat)" /> </div> </td> <td ui-table-cell class="py-3.5 text-right"> <span ui-badge [variant]="badgeVariant(cat)" class="text-xs">{{ cat.statusLabel }}</span> </td> </tr> } </tbody> </table> </div> </div> </div> } </div> <div ui-card class="bg-muted/20 border-border p-4 shadow-xs"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div class="flex items-start gap-3"> <div aria-hidden="true" class="border-success/20 bg-success/10 text-success mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="CheckCircle2" class="size-4" /> </div> <div class="space-y-0.5"> <h4 class="text-foreground text-xs font-semibold">Zero-Based Allocation Completed</h4> <p class="text-muted-foreground text-xs"> Every dollar of your $8,500.00 income is accounted for: $4,450.00 living expenses + $1,583.00 savings goals + $2,467.00 scheduled buffer = $0.00 unassigned. </p> </div> </div> <div class="flex shrink-0 items-center gap-2"> <span ui-badge variant="outline" class="text-xs font-normal">Next Payday: Sep 1, 2026</span> </div> </div> </div> </div> `, }) export class UiPersonalFinanceBudgetComponent { protected readonly Calendar = Calendar protected readonly CheckCircle2 = CheckCircle2 protected readonly Clock = Clock protected readonly CreditCard = CreditCard protected readonly FolderPlus = FolderPlus protected readonly PiggyBank = PiggyBank protected readonly Plus = Plus protected readonly Wallet = Wallet @Input('class') className?: string protected readonly sections = sections get rootClass(): string { return cn('mx-auto w-full max-w-6xl space-y-6', this.className) } format(amount: number): string { return formatCurrency(amount) } percent(cat: EnvelopeCategory): number { return Math.min(100, Math.round((cat.spent / cat.budgeted) * 100)) } asIcon(icon: unknown): never { return icon as never } remainingClass(cat: EnvelopeCategory): string { const remaining = cat.budgeted - cat.spent return cn( 'font-bold tabular-nums', remaining === 0 ? 'text-muted-foreground' : cat.status === 'warning' ? 'text-warning' : 'text-success', ) } progressClass(cat: EnvelopeCategory): string { return cn('h-1.5', this.progressColor(cat.status)) } progressColor(status: EnvelopeStatus): string { if (status === 'warning') return '[&_[data-slot=progress-indicator]]:bg-warning' return '[&_[data-slot=progress-indicator]]:bg-success' } badgeVariant(cat: EnvelopeCategory): 'warning' | 'success' | 'outline' { if (cat.status === 'warning') return 'warning' if (cat.status === 'completed' || cat.status === 'healthy' || cat.status === 'on-track') return 'success' return 'outline' } }src/app/components/blocks/personal-finance-budget/index.ts0.1 kB
export { UiPersonalFinanceBudgetComponent } from './personal-finance-budget.component'
Raw manifest:https://uipkge.dev/r/angular/personal-finance-budget.json