Activity heatmap

See a year of activity at a glance, one square per day.

import { ActivityHeatmap } from "@sagui/ui";
Markdown
Loading demo

When to use

  • Daily rhythm over a long range, like contributions or workouts.
  • Views where streaks and quiet weeks matter more than exact comparison.

When not to use

  • Use bar-chart when the exact comparison is the point.
  • Use calendar to pick dates.

Installation

Install the package and import the styles once. The installation guide covers the Tailwind setup.

$ npm install @sagui/ui

Usage

example.tsx
import { useState } from "react";
import { ActivityHeatmap } from "@sagui/ui";

export function Contributions({ days }: { days: { date: string; count: number }[] }) {
  const [selected, setSelected] = useState<string | null>(null);
  return (
    <ActivityHeatmap days={days} label="Contributions in 2026" period="2026" selectedDate={selected} onSelectDate={setSelected} />
  );
}

Examples

Selecting a day

Pass selectedDate and onSelectDate to ring a day and react to it. weekStartsOn={1} starts weeks on Monday.

Loading demo

API reference

ActivityHeatmap

A contribution style calendar with one tinted square per day and a hover or focus tooltip.

Prop Type Default Description
days (required) { date: string; count: number }[] – One entry per day as YYYY-MM-DD, oldest first. Missing days count as zero.
label (required) string – Accessible name for the grid, such as "Contributions in 2025".
period (required) string – Finishes the summary: "1,284 contributions in {period}".
unit { one: string; other: string } { one: "contribution", other: "contributions" } Nouns for the count.
thresholds [number, number, number] – Upper bounds for levels one to three. Defaults to quarters of the busiest day.
weekStartsOn 0 | 1 0 Sunday or Monday as the first row.
selectedDate string | null null Day drawn with a selection ring.
onSelectDate (date: string) => void – Called on click, Enter, or Space.
actions ReactNode – Controls beside the summary, such as a range switch.
locale string "en-US" Formatting locale.
className string – Class for the root.

Keyboard interactions

Keys Action
ArrowUporArrowDown Moves one day back or forward.
ArrowLeftorArrowRight Moves one week back or forward.
HomeorEnd Jumps to the first or last day.
EnterorSpace Selects the focused day.
Escape Hides the tooltip.

Accessibility

  • The grid is role="grid" with labelled gridcells and a hidden legend explaining the levels.
  • Legend swatches are toggle buttons with aria-pressed that highlight days of one level.
  • The total and hovered day are announced through status and polite live regions; the tooltip itself is aria-hidden.

Motion

  • Cells wave in once on view and a new range recolors the grid in a sweep.
  • The tooltip glides between cells and its text rolls; the total counts to new values.
  • Reduced motion drops the wave, sweep, and glide in favor of short fades.

Responsive behavior

  • Cells scale between 9px and 15px with the container, then the grid scrolls horizontally from the newest week.
  • Below a 440px container the caption switches to a short form.
  • On touch the tooltip lingers after a tap instead of hiding at once.

Performance

  • Every day is its own cell, so a year is about 370 elements; the reveal wave is capped in total time.
  • One shared tooltip serves the whole grid.

Notes

  • Use when rhythm, streaks, and quiet weeks matter more than exact comparison. Use bar-chart when the exact comparison is the point.
  • Pass a range switch through actions and swap days to change the period.

Also in charts