# Habit Tracker

> Habits with their last days as a row of marks: done, missed or no entry, today's mark a toggle.

Component · Life · Pro kit · [Live page](https://ui.twentyfirst.media/pro/components/HabitTracker/)

Habits with their last days as a row of marks: done, missed or no entry, today's mark a toggle.

- `days` is one entry per day, oldest first and today last: `true`, `false` or `null`.
- `streak` shows "12-day streak"; `goal` a short target ("8 glasses").
- `onToggle(id, done)` makes today's mark a button; the other days are read-only and named for
  screen readers ("Mon Done").
- Day labels default to weekday initials ending today; pass `dayLabels` to set them yourself.

## Example

```tsx
import * as React from "react";
import { HabitTracker, Panel, registerIcons } from "@twentyfirstmedia/pro";
import { droplet } from "@twentyfirstmedia/ui-core/icons";

// library icons are registered once by the app
registerIcons({ droplet });
import type { Habit } from "@twentyfirstmedia/pro";

const DAYS = ["M", "T", "W", "T", "F", "S", "S"];

export default function Preview() {
  const [habits, setHabits] = React.useState<Habit[]>([
    {
      id: "walk",
      label: "Morning walk",
      icon: "sun",
      tint: "butter",
      days: [true, true, false, true, true, true, false],
      streak: 3,
      goal: "20 min",
    },
    {
      id: "water",
      label: "Drink water",
      icon: "droplet",
      tint: "sky",
      days: [true, true, true, true, true, true, true],
      streak: 12,
      goal: "8 glasses",
    },
    {
      id: "read",
      label: "Read",
      icon: "book",
      tint: "lilac",
      days: [false, true, null, true, false, true, false],
      goal: "3× a week",
    },
    {
      id: "screens",
      label: "No screens after 10 pm",
      icon: "moon",
      tint: "mint",
      days: [true, false, false, true, true, null, false],
    },
  ]);
  return (
    <Panel title="Habits" subtitle="This week" style={{ maxWidth: 560 }}>
      <HabitTracker
        habits={habits}
        dayLabels={DAYS}
        onToggle={(id, done) =>
          setHabits((list) =>
            list.map((h) =>
              h.id === id
                ? {
                    ...h,
                    days: [...h.days.slice(0, -1), done],
                    streak: done ? (h.streak || 0) + 1 : Math.max(0, (h.streak || 1) - 1),
                  }
                : h,
            ),
          )
        }
      />
    </Panel>
  );
}
```

## Props

### HabitTracker

| Prop | Type | Notes |
| --- | --- | --- |
| `habits` | `Habit[]` | Required |
| `dayLabels?` | `string[]` | Labels of the day columns, oldest first (default: weekday initials ending today). |
| `onToggle?` | `(id: string, done: boolean) => void` | Today's box of a habit was toggled. |
| `label?` | `string` | Accessible name of the list (default "Habits"). |
| `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. |
