# Life Overview

> The start page of an AI-first life app: what needs you today across health, money and papers, as insights with their reasons, the next deadlines, budgets, mood and activity.

Pattern · Halo kit · [Live page](https://ui.twentyfirst.media/halo/patterns/LifeOverviewScreen/)

The start page of an AI-first life app: what needs you today across health, money and papers,
as insights with their reasons, the next deadlines, budgets, mood and activity.


The whole screen is glass over `Ambient`: one `<UIProvider surface="glass">` turns the shell, the
sidebar, panels and the Life cards (vitals, goals, insights, journal entries) into glass, with
inputs and chips on them translucent too. Without the provider the same markup is solid.

## Code

```tsx
import * as React from "react";
import {
  ActivityRings,
  Ambient,
  AppShell,
  BudgetList,
  DeadlineList,
  InsightCard,
  MoodPicker,
  PageHeader,
  Panel,
  SearchField,
  Stagger,
  UIProvider,
} from "@twentyfirstmedia/halo";
import type { MoodLevel, SidebarGroup } from "@twentyfirstmedia/halo";

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 },
    ],
  },
];

/** The start page of an AI-first life app in glass over ambient colour: what needs you today, across health, money and papers. */
export default function Preview() {
  const [mood, setMood] = React.useState<MoodLevel | null>(null);
  return (
    <UIProvider scope="subtree" surface="glass">
      <Ambient />
      <AppShell
        navigation="sidebar"
        brand="Life"
        sidebarGroups={NAV}
        active="today"
        user={{ name: "Mara Ellis", email: "mara@halo.app" }}
        dock={false}
        side={
          <Panel side title="Today">
            <MoodPicker compact value={mood} onChange={setMood} />
            <ActivityRings
              size={96}
              rings={[
                { label: "Move", value: 420, goal: 600, unit: "kcal", tint: "blush" },
                { label: "Exercise", value: 24, goal: 30, unit: "min", tint: "mint" },
                { label: "Stand", value: 9, goal: 12, unit: "h", tint: "sky" },
              ]}
            />
          </Panel>
        }
      >
        <div className="tf-in">
          <PageHeader
            title="Good morning, Mara"
            subtitle="Friday, 13 November · 3 things need you"
            actions={[<SearchField key="s" placeholder="Ask or search" shortcut="⌘K" />]}
          />
        </div>
        <Stagger className="tf-col" style={{ gap: 16 }}>
          <InsightCard
            tone="warning"
            icon="file"
            meta="Documents · 19 days"
            title="Lena's passport expires on 2 December"
            sources={[{ title: "Passport scan", icon: "file" }]}
            why="You travel to Lisbon on 18 December; a valid passport is needed for the flight."
            acceptLabel="Book an appointment"
            onAccept={() => {}}
            onSnooze={() => {}}
            onDismiss={() => {}}
          >
            The town hall has free slots next week; renewal takes about three weeks.
          </InsightCard>
          <InsightCard
            tone="highlight"
            icon="repeat"
            meta="Money · subscriptions"
            title="You pay for two music services"
            sources={[{ title: "Bank account", icon: "card" }]}
            acceptLabel="Cancel one"
            onAccept={() => {}}
            onDismiss={() => {}}
          >
            Keeping one of them saves €131.88 a year.
          </InsightCard>
          <div
            className="tf-grid"
            style={
              {
                "--cols": "repeat(2, minmax(0, 1fr))",
                "--cols-sm": "1fr",
                gap: 16,
              } as React.CSSProperties
            }
          >
            <Panel title="Coming up">
              <DeadlineList
                today={new Date(2026, 10, 13)}
                items={[
                  { id: "car", title: "Car inspection", kind: "Car", date: new Date(2026, 10, 13) },
                  {
                    id: "gym",
                    title: "Gym: last day to cancel",
                    kind: "Contract",
                    date: new Date(2026, 10, 20),
                  },
                  {
                    id: "rent",
                    title: "Rent",
                    kind: "Bill",
                    date: new Date(2026, 11, 1),
                    amount: 1150,
                  },
                ]}
              />
            </Panel>
            <Panel title="Budgets" subtitle="17 days left">
              <BudgetList
                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: "fun",
                    label: "Fun & hobbies",
                    icon: "sparkle",
                    tint: "lilac",
                    spent: 142,
                    limit: 100,
                  },
                ]}
              />
            </Panel>
          </div>
        </Stagger>
      </AppShell>
    </UIProvider>
  );
}
```

## 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. |

### 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. |

### DeadlineList

| Prop | Type | Notes |
| --- | --- | --- |
| `items` | `DeadlineItem[]` | Required |
| `today?` | `Date \| string \| number` | The day to count from (default: now). Set it for server rendering and stable output. |
| `soon?` | `number` | Days ahead that count as soon (default 14). |
| `currency?` | `string` |  |
| `onDone?` | `(id: string) => void` |  |
| `label?` | `string` | Accessible name (default "Deadlines"). |
| `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. |

### MoodPicker

| Prop | Type | Notes |
| --- | --- | --- |
| `value?` | `MoodLevel \| null` |  |
| `defaultValue?` | `MoodLevel \| null` |  |
| `onChange?` | `(mood: MoodLevel) => void` |  |
| `label?` | `ReactNode` | The question (default "How are you feeling?"). |
| `levels?` | `[string, string, string, string, string]` | Words for the five levels, lowest first (default Awful, Low, Okay, Good, Great). |
| `compact?` | `boolean` | Only the faces; the words stay as accessible names. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### ActivityRings

| Prop | Type | Notes |
| --- | --- | --- |
| `rings` | `ActivityRing[]` | Outer ring first. |
| `size?` | `number` | Diameter of the rings (default 132). |
| `legend?` | `boolean` | Hide the legend beside the rings. |
| `label?` | `string` | Accessible name (default "Activity"). |
| `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. |

### SearchField

| Prop | Type | Notes |
| --- | --- | --- |
| `placeholder?` | `string` |  |
| `label?` | `string` | Accessible name (default: the placeholder, or "Search"). |
| `shortcut?` | `string` | The key hint, e.g. "⌘K" (hidden on touch screens). |
| `bindShortcut?` | `boolean` | ⌘/Ctrl + the hint's last key (K for "⌘K") focuses the field, except while typing elsewhere. |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `onChange?` | `(e: ChangeEvent<HTMLInputElement>) => void` |  |
| `onKeyDown?` | `(e: KeyboardEvent<HTMLInputElement>) => void` |  |
| `onFocus?` | `(e: FocusEvent<HTMLInputElement>) => void` |  |
| `onBlur?` | `(e: FocusEvent<HTMLInputElement>) => void` |  |
| `id?` | `string` |  |
| `name?` | `string` |  |
| `autoFocus?` | `boolean` |  |
| `inputProps?` | `InputHTMLAttributes<HTMLInputElement>` | For a search with suggestions (combobox): role, aria-controls, aria-activedescendant … |
| `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` |  |
