# Money

> Household money in the app shell with a sidebar: net worth with its trend, an insight about a duplicate subscription, the month's budgets and the subscriptions beside them.

Pattern · Pro kit · [Live page](https://ui.twentyfirst.media/pro/patterns/MoneyScreen/)

Household money in the app shell with a sidebar: net worth with its trend, an insight about a
duplicate subscription, the month's budgets and the subscriptions beside them.

## Code

```tsx
import * as React from "react";
import {
  AppShell,
  BudgetList,
  Button,
  InsightCard,
  NetWorthCard,
  PageHeader,
  Panel,
  Segmented,
  Stagger,
  SubscriptionList,
} from "@twentyfirstmedia/pro";
import type { SidebarGroup } from "@twentyfirstmedia/pro";

const NAV: SidebarGroup[] = [
  {
    items: [
      { id: "today", label: "Today", icon: "sun" },
      { id: "assistant", label: "Assistant", icon: "sparkle", badge: "dot" },
    ],
  },
  {
    label: "Life",
    items: [
      { id: "health", label: "Health", icon: "heart" },
      { id: "habits", label: "Habits & journal", icon: "check" },
      { id: "money", label: "Money", icon: "wallet" },
      { id: "insurance", label: "Insurance", icon: "shield" },
      { id: "documents", label: "Documents", icon: "folder", count: 2 },
    ],
  },
];

/** Household money: net worth, budgets, subscriptions and what the assistant noticed. */
export default function Preview() {
  const [month, setMonth] = React.useState("November");
  return (
    <AppShell
      navigation="sidebar"
      brand="Life"
      sidebarGroups={NAV}
      active="money"
      user={{ name: "Mara Ellis", email: "mara@halo.app" }}
      dock={false}
      side={
        <Panel side title="Subscriptions" subtitle="5 active">
          <SubscriptionList
            onManage={() => {}}
            items={[
              {
                id: "stream",
                name: "Streaming",
                icon: "play",
                amount: 13.99,
                period: "month",
                next: new Date(2026, 10, 18),
              },
              {
                id: "music",
                name: "Music",
                icon: "monitor",
                amount: 10.99,
                period: "month",
                next: new Date(2026, 10, 21),
              },
              {
                id: "cloud",
                name: "Cloud storage",
                icon: "folder",
                amount: 99,
                period: "year",
                next: new Date(2027, 2, 4),
              },
              {
                id: "gym",
                name: "Gym",
                icon: "zap",
                amount: 34.9,
                period: "month",
                next: new Date(2026, 11, 1),
              },
              {
                id: "news",
                name: "News",
                icon: "book",
                amount: 2.5,
                period: "week",
                status: "trial",
              },
            ]}
          />
        </Panel>
      }
    >
      <div className="tf-in">
        <PageHeader
          title="Money"
          subtitle="All accounts · updated 08:12"
          actions={[
            <Segmented
              key="m"
              label="Month"
              options={["October", "November"]}
              value={month}
              onChange={setMonth}
            />,
            <Button key="a" variant="secondary" icon="plus">
              Add account
            </Button>,
          ]}
        />
      </div>
      <Stagger className="tf-col" style={{ gap: 16 }}>
        <NetWorthCard
          change={1240}
          changeLabel="since October"
          history={[38200, 38900, 39400, 39100, 40300, 41000, 41800, 42900]}
          assets={[
            { label: "Current account", value: 4820, icon: "card" },
            { label: "Savings", value: 12600, icon: "wallet" },
            { label: "ETF portfolio", value: 31450, icon: "chart" },
          ]}
          liabilities={[{ label: "Car loan", value: 11200, icon: "key" }]}
        />
        <InsightCard
          tone="highlight"
          icon="repeat"
          meta="Money · subscriptions"
          title="You pay for two music services"
          sources={[{ title: "Bank account · 6 months", icon: "card" }]}
          why="Two different music subscriptions have been charged every month since May."
          acceptLabel="Cancel one"
          onAccept={() => {}}
          onSnooze={() => {}}
          onDismiss={() => {}}
        >
          Keeping one of them saves €131.88 a year.
        </InsightCard>
        <Panel title="Budgets" subtitle="17 days left in November">
          <BudgetList
            onSelect={() => {}}
            categories={[
              {
                id: "food",
                label: "Groceries",
                icon: "cart",
                tint: "mint",
                spent: 312,
                limit: 450,
              },
              {
                id: "eat",
                label: "Eating out",
                icon: "star",
                tint: "butter",
                spent: 168,
                limit: 180,
              },
              { id: "move", label: "Transport", icon: "map", tint: "sky", spent: 96, limit: 120 },
              {
                id: "fun",
                label: "Fun & hobbies",
                icon: "sparkle",
                tint: "lilac",
                spent: 142,
                limit: 100,
              },
            ]}
          />
        </Panel>
      </Stagger>
    </AppShell>
  );
}
```

## Props

### AppShell

| Prop | Type | Notes |
| --- | --- | --- |
| `active?` | `string` |  |
| `brand?` | `ReactNode` | The product name, or a node (a logo mark with the name; collapsed sidebars show it whole). |
| `tag?` | `string` |  |
| `nav?` | `NavItem[]` |  |
| `user?` | `{ name: string; email?: string }` |  |
| `onNavigate?` | `(label: string) => void` |  |
| `topExtra?` | `ReactNode` |  |
| `sheetStyle?` | `CSSProperties` |  |
| `contentKey?` | `string \| number` |  |
| `side?` | `ReactNode` |  |
| `dock?` | `boolean` |  |
| `dockItems?` | `DockProps["items"]` |  |
| `railTop?` | `RailItem[]` |  |
| `railBottom?` | `RailItem[]` |  |
| `navigation?` | `"top" \| "sidebar"` | `top` (default): navigation in the top bar, tools in the side rail. `sidebar`: a labelled sidebar on the frame, with groups, nesting and a drawer on phones. |
| `sidebarGroups?` | `SidebarGroup[]` | With `navigation="sidebar"`: groups instead of the flat `nav` list. |
| `sidebar?` | `Partial<Omit<SidebarProps, "variant" \| "groups">>` | With `navigation="sidebar"`: more sidebar props (collapsed, storageKey, shortcut, linkAs …). |
| `workspaces?` | `WorkspaceSwitcherProps` | With `navigation="sidebar"`: the workspace switcher at the top instead of the brand. |
| `userMenu?` | `(MenuItem \| "-")[]` | The account menu's entries (with `navigation="sidebar"` at the foot of the sidebar). |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### NetWorthCard

| Prop | Type | Notes |
| --- | --- | --- |
| `surface?` | `Surface` | `"glass"`: translucent glass over a backdrop (`Ambient`, a photo, a map); `"solid"` opts out inside a glass `UIProvider`. |
| `assets` | `{ label: string; value: number; icon?: IconName }[]` | Required |
| `liabilities?` | `{ label: string; value: number; icon?: IconName }[]` |  |
| `history?` | `number[]` | Net worth over time, oldest first. |
| `change?` | `number` | Change over `changeLabel` (e.g. +1,240 "since October"). |
| `changeLabel?` | `string` |  |
| `currency?` | `string` |  |
| `title?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### InsightCard

| Prop | Type | Notes |
| --- | --- | --- |
| `surface?` | `Surface` | `"glass"`: translucent glass over a backdrop (`Ambient`, a photo, a map); `"solid"` opts out inside a glass `UIProvider`. |
| `title` | `string` | Required |
| `children?` | `ReactNode` |  |
| `icon?` | `IconName` |  |
| `tone?` | `"info" \| "success" \| "warning" \| "highlight"` |  |
| `meta?` | `ReactNode` | Above the title: area and time ("Money · 2 h ago"). |
| `sources?` | `Source[]` | Where the insight comes from, as chips (like chat sources). |
| `why?` | `ReactNode` | Why the assistant shows it; folded away behind "Why am I seeing this?". |
| `onAccept?` | `() => void` |  |
| `acceptLabel?` | `string` |  |
| `onSnooze?` | `() => void` |  |
| `onDismiss?` | `() => void` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### BudgetList

| Prop | Type | Notes |
| --- | --- | --- |
| `categories` | `BudgetCategory[]` | Required |
| `currency?` | `string` |  |
| `onSelect?` | `(id: string) => void` | A category was picked (to show its transactions). |
| `label?` | `string` | Accessible name (default "Budgets"). |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### SubscriptionList

| Prop | Type | Notes |
| --- | --- | --- |
| `items` | `Subscription[]` | Required |
| `currency?` | `string` |  |
| `onManage?` | `(id: string) => void` | "Manage" on a subscription (cancel, change plan …). |
| `totals?` | `boolean` | Hide the monthly and yearly totals. |
| `label?` | `string` | Accessible name (default "Subscriptions"). |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### PageHeader

| Prop | Type | Notes |
| --- | --- | --- |
| `crumbs?` | `(string \| Crumb)[]` | A breadcrumb above the title (see `Breadcrumb`): labels, or items with `href`/`onClick`. |
| `crumbLinkAs?` | `ElementType` | The element crumb links render as, e.g. Next.js `Link`. |
| `title` | `string` | Required |
| `subtitle?` | `string` |  |
| `actions?` | `ReactNode[]` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Panel

| Prop | Type | Notes |
| --- | --- | --- |
| `side?` | `boolean` |  |
| `title?` | `string` |  |
| `subtitle?` | `string` |  |
| `action?` | `ReactNode` |  |
| `children?` | `ReactNode` |  |
| `surface?` | `Surface` | `"glass"`: translucent glass over what is behind (maps, 3D, video, photos); `"solid"` opts out inside a glass `UIProvider`. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Segmented

| Prop | Type | Notes |
| --- | --- | --- |
| `options` | `(string \| Option)[]` | Required |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `onChange?` | `(v: string) => void` |  |
| `size?` | `"sm"` |  |
| `block?` | `boolean` |  |
| `label?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Stagger

| Prop | Type | Notes |
| --- | --- | --- |
| `as?` | `string` |  |
| `className?` | `string` |  |
| `style?` | `CSSProperties` |  |
| `delay?` | `number` | Extra delay before the first child, in ms. |
| `reveal?` | `boolean` | Below the fold, wait until the group scrolls into view (default true). |
| `children?` | `ReactNode` |  |

### Button

| Prop | Type | Notes |
| --- | --- | --- |
| `variant?` | `"primary" \| "secondary" \| "soft" \| "ghost" \| "danger" \| "danger-solid"` |  |
| `size?` | `"md" \| "sm"` |  |
| `icon?` | `IconName` |  |
| `iconRight?` | `IconName` |  |
| `disabled?` | `boolean` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `children?` | `ReactNode` |  |
| `asChild?` | `boolean` | Render the single child element (a link, a router Link) with the button's look instead of a <button>. |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
