Mikkel Manniche
Dansk English Deutsch

Template · React · HTML

Account home

A bank account start page in Tiles: a payment card, the balance, a three-state dial and recent transactions.

Preview

React

Install the template with shadcn. It lands in your project as ordinary components that you edit yourself.

npx shadcn@latest add https://mikkelmanniche.dk/lab/r/account-home.json
npx shadcn@latest add @manniche/account-home
account-home.tsx
// An account home screen in Tiles: the payment card, the balance, a three-state spending dial and recent transactions.
import { cn } from '@/lib/utils'
import { BigNumber, DeltaPill } from '@/registry/manniche/chart-kit/chart-kit'
import { formatValue, type ValueFormat } from '@/registry/manniche/chart-kit/chart-utils'
import { DataTile, TileFact } from '@/registry/manniche/data-tile/data-tile'
import { CardStack, type StackCard } from '@/registry/manniche/card-stack/card-stack'
import { Dial, type DialZone } from '@/registry/manniche/dial/dial'
import { TransactionList, type Transaction } from '@/registry/manniche/transaction-list/transaction-list'

export type AccountHomeProps = {
  data: {
    card: StackCard
    balance: number
    change: number
    /** Spending pace against the budget: 1 is exactly on budget. */
    pace: number
    budget: number
    transactions: Transaction[]
    categories?: Record<string, string>
  }
  format?: ValueFormat
  title?: string
  note?: string
  /** "YYYY-MM-DD" of today, so the list reads "Today" and "Yesterday". Default the day of `now`. */
  today?: string
  /** The current time. Used for `today` when that is not given. */
  now?: Date
  labels?: { card?: string; balance?: string; pace?: string; paceCaption?: string; transactions?: string; budget?: string; zones?: [string, string, string] }
  className?: string
}

const DEFAULT_FORMAT: ValueFormat = { currency: 'EUR', decimals: 2 }

/** Account home: card, balance, spending pace dial (under, on track, over) and transactions. */
// "YYYY-MM-DD" of `now` in local time, for TransactionList's "Today" and "Yesterday".
const dayOf = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`

export function AccountHome({ data, format = DEFAULT_FORMAT, title = 'Account', note = 'Example data.', today, now, labels, className }: AccountHomeProps) {
  const day = today ?? (now ? dayOf(now) : undefined)
  const l = { card: 'Card', balance: 'Balance', pace: 'Spending pace', paceCaption: 'of your daily budget', transactions: 'Recent transactions', budget: 'Budget this month', zones: ['Under', 'On track', 'Over'] as [string, string, string], ...labels }
  const zones: DialZone[] = [
    { label: l.zones[0], to: 0.85, color: 'var(--chart-1)' },
    { label: l.zones[1], to: 1.1, color: 'var(--success)' },
    { label: l.zones[2], to: 1.6, color: 'var(--destructive)' },
  ]
  return (
    <div className={cn('@container w-full font-sans text-foreground', className)}>
      <div className="grid gap-4 @3xl:grid-cols-6 @6xl:grid-cols-12">
        <h1 className="text-[22px] leading-tight font-semibold tracking-tight @3xl:col-span-6 @6xl:col-span-12">{title}</h1>
        <DataTile title={l.card} className="@3xl:col-span-3 @6xl:col-span-4">
          <CardStack data={[data.card]} label={l.card} format={format} hideAmount />
        </DataTile>
        <DataTile title={l.balance} inverted className="@3xl:col-span-3 @6xl:col-span-4" footer={<span>{note}</span>}>
          <BigNumber value={data.balance} format={format} size="lg" />
          <div className="mt-3">
            <DeltaPill value={data.change} format={{ decimals: 1, suffix: '%', sign: true }} />
          </div>
          <div className="mt-5">
            <TileFact label={l.budget}>{formatValue(data.budget, { ...format, decimals: 2 })}</TileFact>
          </div>
        </DataTile>
        <DataTile title={l.pace} className="@3xl:col-span-6 @6xl:col-span-4">
          <Dial value={data.pace} zones={zones} format={{ decimals: 2, suffix: '×' }} label={l.pace} caption={l.paceCaption} />
        </DataTile>
        <DataTile title={l.transactions} className="@3xl:col-span-6 @6xl:col-span-12">
          <TransactionList data={data.transactions} label={l.transactions} today={day} format={format} categories={data.categories} limit={5} />
        </DataTile>
      </div>
    </div>
  )
}

export default AccountHome
account-home-demo.tsx
import { AccountHome } from '@/components/account-home'

// Example data for the demo, not a real account. "Today" is Thursday 8 October 2026.
const data = {
  card: { id: 'everyday', name: 'Everyday', kind: 'Debit', ending: '4821', amount: 2091.2 },
  balance: 2091.2,
  change: 1.8,
  pace: 0.92,
  budget: 1800,
  categories: {
    Groceries: 'var(--chart-1)',
    Transport: 'var(--chart-2)',
    'Eating out': 'var(--chart-3)',
    Shopping: 'var(--chart-4)',
    Bills: 'var(--chart-5)',
  },
  transactions: [
    { id: 't1', name: 'Harbour Market', category: 'Groceries', date: '2026-10-08T12:58', amount: -48.2, status: 'pending' as const },
    { id: 't2', name: 'City Transit', category: 'Transport', date: '2026-10-08T08:31', amount: -3.1 },
    { id: 't3', name: 'Corner Bakery', category: 'Eating out', date: '2026-10-08T08:12', amount: -6.4 },
    { id: 't5', name: 'Northwind Books', category: 'Shopping', date: '2026-10-07T16:05', amount: -31.5 },
    { id: 't6', name: 'From Savings', category: 'Transfer', date: '2026-10-07T09:00', amount: 200 },
    { id: 't7', name: 'Lumen Energy', category: 'Bills', date: '2026-10-06T07:00', amount: -74 },
  ],
}

export default function AccountHomeDemo() {
  return <AccountHome data={data} today="2026-10-08" />
}

HTML

The whole page as one file with all CSS built in and no JavaScript. Toggles, tabs and filters work with HTML and CSS alone.

Open in a new tab

The colour in your own app

If you already have shadcn set up, these variables give the same colour. Light in :root, dark in .dark.

:root {
  --primary: oklch(0.52 0.2 262);
  --primary-foreground: oklch(0.99 0 0);
  --accent: oklch(0.95 0.034 262);
  --accent-foreground: oklch(0.36 0.140 262);
  --ring: oklch(0.52 0.2 262);
}

.dark {
  --primary: oklch(0.74 0.150 262);
  --primary-foreground: oklch(0.18 0.024 262);
  --accent: oklch(0.29 0.050 262);
  --accent-foreground: oklch(0.9 0.050 262);
  --ring: oklch(0.74 0.150 262);
}

Install

npx shadcn@latest add https://mikkelmanniche.dk/lab/r/account-home.json

Dependencies

    Files

    • account-home.tsx
    • account-home.html

    Manniche Lab · open source

    Free tools for AI agents and web development.