# Wellbeing

> A wellbeing dashboard in glass: sleep, streak and steps as tinted glass tiles, last nights' sleep, today's goal, mindful minutes, mood and habits, and a journal prompt, over the kit's ambient colours.

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

A wellbeing dashboard in glass: sleep, streak and steps as tinted glass tiles, last nights' sleep, today's goal, mindful minutes, mood and habits, and a journal prompt, over the kit's ambient colours.

- The whole app shell is glass: top bar, rail, panels, tiles and dock float over `Ambient`.
- Built from `UIProvider surface="glass"`, `Ambient`, `AppShell`, `StatTile`, `BarChart`, `ProgressRing`, `AreaChart`, `Segmented` and `Checkbox`.

## Code

```tsx
import * as React from "react";
import {
  Ambient,
  AppShell,
  AreaChart,
  BarChart,
  Button,
  Checkbox,
  PageHeader,
  Panel,
  ProgressRing,
  Segmented,
  Stagger,
  StatTile,
  Textarea,
  UIProvider,
} from "@twentyfirstmedia/halo";

/** A wellbeing dashboard in glass over the kit's ambient colours. */
function App() {
  return (
    <UIProvider scope="subtree" surface="glass">
      <Ambient />
      <AppShell
        brand="Bloom"
        tag="Daily"
        active="Today"
        user={{ name: "Mara Ellis", email: "mara@bloom.app" }}
        nav={[
          { label: "Today", icon: "sun" },
          { label: "Sleep", icon: "moon" },
          { label: "Habits", icon: "check" },
          { label: "Journal", icon: "book" },
        ]}
        railTop={[
          { label: "Today", icon: "home", active: true },
          { label: "Trends", icon: "trend" },
          { label: "Reminders", icon: "bell", count: 2 },
        ]}
        railBottom={[{ label: "Settings", icon: "settings" }]}
        dockItems={[
          { icon: "heart", label: "Log mood", tint: "blush" },
          { icon: "moon", label: "Log sleep", tint: "sky" },
          { icon: "zap", label: "Start a workout", tint: "butter" },
          { icon: "sparkle", label: "Breathe", tint: "lilac" },
        ]}
        side={
          <Panel side title="Journal" subtitle="One line is enough">
            <Textarea
              label="What are you grateful for today?"
              rows={4}
              defaultValue="Coffee on the balcony before everyone woke up."
            />
            <Button variant="primary" icon="check">
              Save entry
            </Button>
          </Panel>
        }
      >
        <div className="tf-in">
          <PageHeader
            title="Good morning, Mara"
            subtitle="Thursday · You slept 7 h 42 min"
            actions={[
              <Button key="log" variant="secondary" icon="plus">
                Log activity
              </Button>,
            ]}
          />
        </div>
        <Stagger
          className="tf-grid"
          style={
            {
              "--cols": "repeat(3, minmax(0, 1fr))",
              "--cols-sm": "1fr",
              gap: 16,
            } as React.CSSProperties
          }
        >
          <StatTile value="7h 42m" label="Sleep" tint="sky" delta="+18 min" />
          <StatTile value="12 days" label="Streak" tint="butter" delta="Best 21" />
          <StatTile value="8,240" label="Steps" tint="mint" delta="82% of goal" />
        </Stagger>
        <Stagger
          className="tf-grid"
          delay={200}
          style={
            {
              "--cols": "minmax(0, 3fr) minmax(0, 2fr)",
              "--cols-sm": "1fr",
              gap: 16,
              marginTop: 16,
            } as React.CSSProperties
          }
        >
          <Panel title="Sleep" subtitle="Last 7 nights, in hours">
            <BarChart
              label="Hours of sleep"
              height={150}
              data={[
                { label: "Fri", value: 6.8, tint: "sky" },
                { label: "Sat", value: 8.1, tint: "sky" },
                { label: "Sun", value: 7.9, tint: "sky" },
                { label: "Mon", value: 6.4, tint: "lilac" },
                { label: "Tue", value: 7.2, tint: "sky" },
                { label: "Wed", value: 7.5, tint: "sky" },
                { label: "Thu", value: 7.7, tint: "mint" },
              ]}
            />
          </Panel>
          <Panel title="Today's goal" subtitle="Move, breathe, reflect">
            <div style={{ display: "grid", placeItems: "center", padding: "8px 0" }}>
              <ProgressRing
                value={72}
                tint="mint"
                caption="of today"
                label="Daily goal"
                size={132}
              />
            </div>
          </Panel>
          <Panel title="Mindful minutes" subtitle="This week">
            <AreaChart
              label="Mindful minutes"
              labels={["Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu"]}
              series={[{ name: "Minutes", values: [6, 12, 15, 4, 10, 14, 18], tint: "lilac" }]}
              height={130}
            />
          </Panel>
          <Panel title="How do you feel?" subtitle="3 of 4 habits done">
            <Segmented
              label="Mood"
              block
              defaultValue="Calm"
              options={["Tired", "Calm", "Great"]}
            />
            <Checkbox defaultChecked>Drink 2 l of water</Checkbox>
            <Checkbox defaultChecked>10 minutes outside</Checkbox>
            <Checkbox defaultChecked>Stretch after lunch</Checkbox>
            <Checkbox>Phone away at 22:00</Checkbox>
          </Panel>
        </Stagger>
      </AppShell>
    </UIProvider>
  );
}

export default function Preview() {
  return <App />;
}
```

## Props

### Ambient

| Prop | Type | Notes |
| --- | --- | --- |
| `colors?` | `string[]` | Colours of the soft fields (any CSS colour); default the kit's `--ambient-1…4`. |
| `src?` | `string` | A photo (or, with `video`, a video) instead of the fields, under a scrim that keeps it calm. |
| `video?` | `boolean` | Treat `src` as a video (muted, looping, still with motion off). |
| `animate?` | `boolean` | Let the fields drift slowly (default true). Still with motion off; paused off screen. |
| `fixed?` | `boolean` | Cover the window behind the page instead of the nearest container. |
| `children?` | `ReactNode` | Your own layer (a canvas, WebGL, a map) instead of fields or media. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

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

### AreaChart

| Prop | Type | Notes |
| --- | --- | --- |
| `labels` | `string[]` | Required |
| `series` | `Series[]` | Required |
| `stacked?` | `boolean` |  |
| `height?` | `number` |  |
| `width?` | `number` |  |
| `format?` | `(v: number) => string` |  |
| `legend?` | `boolean` |  |
| `label?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### BarChart

| Prop | Type | Notes |
| --- | --- | --- |
| `data` | `{ label: string; value: number; tint?: Tint \| "ink" }[]` | Required |
| `tint?` | `Tint \| "ink"` |  |
| `height?` | `number` |  |
| `width?` | `number` |  |
| `barWidth?` | `number` |  |
| `format?` | `(v: number) => string` |  |
| `label?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

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

### Checkbox

| Prop | Type | Notes |
| --- | --- | --- |
| `name?` | `string` |  |
| `value?` | `string` |  |
| `required?` | `boolean` |  |
| `checked?` | `boolean` |  |
| `defaultChecked?` | `boolean` |  |
| `onChange?` | `(e: ChangeEvent<HTMLInputElement>) => void` |  |
| `children?` | `ReactNode` |  |
| `disabled?` | `boolean` |  |
| `label?` | `string` |  |
| `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. |

### ProgressRing

| Prop | Type | Notes |
| --- | --- | --- |
| `value` | `number` | Required |
| `tint?` | `Tint \| "ink"` |  |
| `size?` | `number` |  |
| `stroke?` | `number` |  |
| `caption?` | `string` |  |
| `label?` | `string` |  |
| `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` |  |

### StatTile

| Prop | Type | Notes |
| --- | --- | --- |
| `value` | `ReactNode` | Required |
| `label` | `string` | Required |
| `tint?` | `Tint \| "surface" \| "placeholder"` |  |
| `delta?` | `string` |  |
| `delay?` | `number` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Textarea

| Prop | Type | Notes |
| --- | --- | --- |
| `placeholder?` | `string` |  |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `rows?` | `number` |  |
| `disabled?` | `boolean` |  |
| `onChange?` | `(e: ChangeEvent<HTMLTextAreaElement>) => void` |  |
| `label?` | `string` |  |
| `hint?` | `string` |  |
| `error?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
