# ASCII Heatmap

> A contribution-style calendar grid in shade blocks.

Category: Charts. Docs: https://asciicn.sumak.dev/docs/ascii-heatmap

## Installation

```bash
npx shadcn@latest add @asciicn/ascii-heatmap
```

Needs the `@asciicn` registry in components.json: `"registries": { "@asciicn": "https://asciicn.sumak.dev/r/{name}.json" }`.

## Usage

```tsx
import { AsciiHeatmap } from "@/components/ascii/ascii-heatmap"

<AsciiHeatmap
  label="Contributions"
  data={[{ date: "2026-10-26", value: 4 }, { date: "2026-10-25", value: 1 }]}
  className="text-primary"
/>
```

## Examples

### A habit tracker

Twelve weeks and a fixed `max` turn it into a training log. `formatDay` is exported for readouts.

```tsx
import { AsciiHeatmap, formatDay } from "@/components/ascii/ascii-heatmap"

<AsciiHeatmap
  label="Running"
  data={runs}
  weeks={12}
  max={10}
  format={(km, date) => `${km} km on ${formatDay(date)}`}
  className="text-sm text-sky-500"
/>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `{ date, value }[]` |  | One entry per day, `date` as `YYYY-MM-DD`. Missing days count as 0. |
| `label` | `string` |  | Names the chart for screen readers. |
| `weeks` | `number` | `53` | Columns to show, ending on the week of `end`. The oldest are dropped when the container is too narrow. |
| `end` | `string | Date` | `latest date` | Last day shown. Later days in that week stay empty. |
| `max` | `number` | `largest value` | Value that gets the darkest shade. Set it to compare several heatmaps fairly. |
| `format` | `(value, date) => string` | `"4 on Mon, Oct 26, 2026"` | Readout for the hovered or focused day. |
| `legend` | `boolean` | `true` | Show the less to more key. |
| `glyphs` | `string` | `"shade"` | How the days look: `shade`, `hash`, `dollar`, `diamond` or `circle`, or any five glyphs from nothing to the busiest day. |
| `animate` | `boolean` | `true` | Ripple the days in from the top left the first time the grid scrolls into view. |

## Notes

- **Accessibility.** A focusable group named by `label`. Arrow keys walk the days, Escape clears, and the readout is a live region.
- **Shades.** Five levels: `·` for nothing, then `░▒▓█` in even steps up to `max`, or the five glyphs of another look from `heatmapGlyphs`. Each glyph is clipped to a 1ch square, so cells stay square and evenly spaced in any font.
- **Sizing.** Fills its container's width and shows as many of the latest `weeks` as fit, so a full year narrows to recent months on a phone.
- **Dates.** Handled as UTC days, so the grid never shifts with the viewer's time zone. Weeks start on Sunday.
- **Motion.** Each day scales and fades in with a delay that grows along the diagonal, once. Under `prefers-reduced-motion` the grid appears finished.
- **Dependencies.** `motion` for the entrance.
