# heat-graph
URL: /docs/utilities/heat-graph

Headless, composable activity heatmap components for React.

import { Callout } from "fumadocs-ui/components/callout";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
import { HeatGraphDemo } from "@/app/heat-graph/heat-graph-demo";

`heat-graph` provides headless, Radix-style primitives for building GitHub-style activity heatmap graphs.

* **Composable** — Radix-style compound components you fully control
* **Headless** — Zero styling opinions, bring your own CSS/Tailwind
* **Tooltip built-in** — Powered by Radix Popper for positioning
* **Customizable bucketing** — Plug in your own classification function

<HeatGraphDemo />

Installation \[#installation]

<Tabs items={["shadcn", "npm", "pnpm", "yarn"]}>
  <Tab value="shadcn">
    ```sh
    npx shadcn@latest add https://r.assistant-ui.com/heat-graph
    ```

    This installs a pre-styled `HeatGraph` component to `components/assistant-ui/heat-graph.tsx` along with the `heat-graph` package.
  </Tab>

  <Tab value="npm">
    ```sh
    npm install heat-graph
    ```
  </Tab>

  <Tab value="pnpm">
    ```sh
    pnpm add heat-graph
    ```
  </Tab>

  <Tab value="yarn">
    ```sh
    yarn add heat-graph
    ```
  </Tab>
</Tabs>

Quick Start \[#quick-start]

```tsx title="components/activity-graph.tsx"
"use client";

import * as HeatGraph from "heat-graph";

const COLORS = ["#ebedf0", "#c6d7f9", "#8fb0f3", "#5888e8", "#2563eb"];

export function ActivityGraph({ data }: { data: HeatGraph.DataPoint[] }) {
  return (
    <HeatGraph.Root data={data} weekStart="monday" colorScale={COLORS}>
      <HeatGraph.Grid className="gap-[3px]">
        {({ cells }) =>
          cells.map((cell) => (
            <HeatGraph.Cell
              key={`${cell.column}-${cell.row}`}
              className="aspect-square rounded-sm"
            />
          ))
        }
      </HeatGraph.Grid>
      <HeatGraph.Tooltip>
        {({ cell }) => (
          <div>
            {cell.count} contributions on {cell.date.toLocaleDateString()}
          </div>
        )}
      </HeatGraph.Tooltip>
    </HeatGraph.Root>
  );
}
```

<Callout type="info">
  Components using Heat Graph must be Client Components (`"use client"`), since they rely on React Context and interactivity.
</Callout>

Anatomy \[#anatomy]

```tsx
import * as HeatGraph from "heat-graph";

<HeatGraph.Root data={data} colorScale={colors}>

  {/* Month labels */}
  <HeatGraph.MonthLabels>
    {({ labels, totalWeeks }) => labels.map((l) => (
      <span key={l.column} style={{ left: `${(l.column / totalWeeks) * 100}%` }}>
        {HeatGraph.MONTH_SHORT[l.month]}
      </span>
    ))}
  </HeatGraph.MonthLabels>

  {/* Day-of-week labels */}
  <HeatGraph.DayLabels>
    {({ labels }) => labels.map((l) => (
      <span key={l.row}>{HeatGraph.DAY_SHORT[l.dayOfWeek]}</span>
    ))}
  </HeatGraph.DayLabels>

  {/* Grid + Cells */}
  <HeatGraph.Grid>
    {({ cells }) => cells.map((cell) => (
      <HeatGraph.Cell key={`${cell.column}-${cell.row}`} />
    ))}
  </HeatGraph.Grid>

  {/* Legend */}
  <HeatGraph.Legend>
    {({ items }) => items.map((item) => (
      <HeatGraph.LegendLevel key={item.level} />
    ))}
  </HeatGraph.Legend>

  {/* Tooltip */}
  <HeatGraph.Tooltip>
    {({ cell }) => <div>{cell.count} on {cell.date.toLocaleDateString()}</div>}
  </HeatGraph.Tooltip>

</HeatGraph.Root>
```

API Reference \[#api-reference]

Root \[#root]

The top-level provider. Renders a `<div>` that computes the grid layout and provides state to all children. Accepts all standard div props.

| Prop         | Type                   | Default             | Description                                        |
| ------------ | ---------------------- | ------------------- | -------------------------------------------------- |
| `data`       | `DataPoint[]`          | required            | Array of `{ date: string \| Date, count: number }` |
| `start`      | `string \| Date`       | 1 year before `end` | Start of the date range                            |
| `end`        | `string \| Date`       | today               | End of the date range                              |
| `weekStart`  | `"sunday" \| "monday"` | `"sunday"`          | First day of the week                              |
| `classify`   | `ClassifyFn`           | `autoLevels(5)`     | Bucketing function mapping counts to levels        |
| `colorScale` | `string[]`             | —                   | Array of colors, one per level (index 0 = lowest)  |

Grid \[#grid]

A `<div>` with CSS Grid layout. Renders `gridTemplateColumns` and `gridTemplateRows` based on the computed data. Accepts all standard div props.

Provides a `CellCollection` via render prop. The `.map()` callback receives a `CellData` object and automatically wraps each element in the context needed by `Cell`.

```ts
type CellData = {
  date: Date;
  count: number;
  level: number;
  column: number;
  row: number;
};
```

Cell \[#cell]

A `<div>` that reads from cell context. Automatically applies:

* Grid positioning (`gridColumn`, `gridRow`)
* Background color from `colorScale`
* Tooltip hover handlers

Accepts all standard div props. Pass `colorScale` to override the Root-level color scale.

MonthLabels \[#monthlabels]

Render prop component providing month label data.

```tsx
<HeatGraph.MonthLabels>
  {({ labels, totalWeeks }) =>
    labels.map((label) => (
      <span
        key={label.column}
        style={{ left: `${(label.column / totalWeeks) * 100}%` }}
      >
        {HeatGraph.MONTH_SHORT[label.month]}
      </span>
    ))
  }
</HeatGraph.MonthLabels>
```

Provides `{ labels, totalWeeks }`. Each label has `{ month: number, column: number }`. Use `totalWeeks` to compute label positions. Use `MONTH_SHORT[label.month]` for English labels, or format with `Intl.DateTimeFormat` for localization.

DayLabels \[#daylabels]

Render prop component providing day-of-week label data.

```tsx
<HeatGraph.DayLabels>
  {({ labels }) =>
    labels.map((label) => (
      <span key={label.row}>{HeatGraph.DAY_SHORT[label.dayOfWeek]}</span>
    ))
  }
</HeatGraph.DayLabels>
```

Each label has `{ dayOfWeek: number, row: number }` where `dayOfWeek` is 0=Sun..6=Sat. Use `DAY_SHORT[label.dayOfWeek]` for English labels, or format with `Intl.DateTimeFormat` for localization.

Legend \[#legend]

Render prop component providing legend items. Each item has `{ level: number, color: string | undefined }`.

LegendLevel \[#legendlevel]

A `<div>` that reads from legend item context. Automatically applies `backgroundColor` from the color scale. Use inside `Legend`'s `.map()`.

Tooltip \[#tooltip]

Renders only when a cell is hovered. Positioned by Radix Popper relative to the hovered cell. Accepts Radix Popper `Content` props (`side`, `sideOffset`, `align`, etc.).

```tsx
<HeatGraph.Tooltip side="top" sideOffset={8} className="...">
  {({ cell }) => <div>{cell.count} contributions</div>}
</HeatGraph.Tooltip>
```

autoLevels(n) \[#autolevelsn]

Default classification function. Maps counts into `n` evenly-distributed levels (0 to n-1). Level 0 is always count 0.

```ts
type ClassifyFn = (counts: number[]) => (count: number) => number;
```

To provide a custom classifier:

```tsx
const myClassify: HeatGraph.ClassifyFn = (counts) => {
  const p75 = percentile(counts, 75);
  return (count) => {
    if (count === 0) return 0;
    if (count < p75 * 0.25) return 1;
    if (count < p75 * 0.5) return 2;
    if (count < p75) return 3;
    return 4;
  };
};

<HeatGraph.Root data={data} classify={myClassify}>
```

MONTH\_SHORT \[#month\_short]

English month abbreviations array: `["Jan", "Feb", ..., "Dec"]`. Index by `MonthLabel.month`.

DAY\_SHORT \[#day\_short]

English day abbreviations array: `["Sun", "Mon", ..., "Sat"]`. Index by `DayLabel.dayOfWeek`.
