CalendarHeatmap

A calendar grid that shades each day by its value.

StableSince 1.0.0
On this page

Import

import { CalendarHeatmap } from '@envsemble/envui';

Usage

CalendarHeatmap takes data, one entry per day with its date, as YYYY-MM-DD, its value, which chooses the color step, and a label with the value in words and its unit, such as 4 deployments. Days without an entry count as 0. startDate and endDate set the days shown, from the earliest to the latest day in data by default. Name the grid with aria-label, or aria-labelledby for a visible heading.

Deployments.tsx
import { CalendarHeatmap, type CalendarHeatmapDay } from '@envsemble/envui';

/** Stands in for a year of deployment counts: busier midweek, with quiet stretches. */
function deploymentsPerDay(): CalendarHeatmapDay[] {
  const end = Date.UTC(2026, 2, 31);
  return Array.from({ length: 365 }, (_, index) => {
    const time = end - (364 - index) * 86_400_000;
    const weekday = new Date(time).getUTCDay();
    const wave = Math.sin(index / 9) + Math.sin((index * 5) / 23) + 0.6;
    const count = Math.max(0, Math.round(wave * 3 * (weekday === 0 || weekday === 6 ? 0.3 : 1)));
    return {
      date: new Date(time).toISOString().slice(0, 10),
      value: count,
      label: `${count} ${count === 1 ? 'deployment' : 'deployments'}`,
    };
  });
}

const data = deploymentsPerDay();

export default function Deployments() {
  return <CalendarHeatmap aria-label="Deployments in the last year" data={data} tone="success" />;
}

Tones and color steps

tone is primary, the default, accent, success, warning, danger, done or neutral: the tone the colors are mixed from. levels sets the color steps, the empty one included, from 2 to 10, and 5 by default; maxValue sets the value that takes the strongest color, the largest value shown by default.

Color by rank

thresholds sets the lowest value of each color step above the empty one, in ascending order, up to nine, such as quantiles of the data, as ColorByRank computes them; they replace levels and maxValue.

<CalendarHeatmap aria-label="Edits by rank" data={data} thresholds={[1, 3, 5, 8]} />

Tooltip

renderTooltip receives the day, with its date, value, label and level, the color step from 0, and returns the tooltip's content; the day's accessible name keeps its date and value. formatValue and formatDate write a value and a date.

<CalendarHeatmap
  aria-label="Reviews in ten weeks"
  data={data}
  formatValue={reviews}
  renderTooltip={({ date, value }) => (
    <>
      <strong>{reviews(value)}</strong>
      <br />
      {dates.format(new Date(`${date}T00:00:00Z`))}
    </>
  )}
/>

Languages

EnvUIProvider's locale names the months, weekdays and dates, through Intl; monthLabels, weekdayLabels, lessLabel and moreLabel set them for one heatmap. Weeks start on the locale's first day, as on Calendar; weekStart sets another, from 0 for Sunday to 6 for Saturday, such as weekStart={1} for Monday.

<EnvUIProvider messages={{ calendarHeatmap: { less: 'Quieter', more: 'Busier' } }}>
  <CalendarHeatmap aria-label="Activity in the first quarter" data={data} weekStart={1} />
</EnvUIProvider>

Composition

The parts are the month names, env-calendar-heatmap-month, the weekday names, env-calendar-heatmap-weekday, the days, env-calendar-heatmap-day, and the legend, env-calendar-heatmap-legend, with a swatch, env-calendar-heatmap-swatch, for each step. The tooltip is env-calendar-heatmap-tooltip, the shared tooltip popup. Put the exact figures elsewhere, such as a Table, where people compare them.

Accessibility

You provide

  • A name for the heatmap, and values whose names say their unit.
  • The exact figures elsewhere, where people compare them.

envUI provides

  • A read-only grid with a cell for each day, named with its date and value.
  • One tab stop, the tooltip on focus as on hover, and a focus outline.
  • Month and weekday names and the legend hidden; each day's name says it all.

Props

CalendarHeatmap

A calendar of daily values with weeks as columns and weekdays as rows, colored in steps of a tone. Each day names its date and value, shows them in a tooltip on hover and focus, and the grid is one tab stop that the arrow keys move through.

PropTypeDescription
datarequired
readonly CalendarHeatmapDay[]
Values by day; days without an entry count as 0.
startDate
string
The first day shown, as YYYY-MM-DD; the earliest day in data by default.
endDate
string
The last day shown, as YYYY-MM-DD; the latest day in data by default.
levelsDefault 5
number
Color steps including the empty step, 2 to 10; 5 by default.
maxValue
number
The value that takes the strongest color; the largest value shown by default.
thresholds
readonly number[]
The lowest value of each color step above the empty one, in ascending order, such as quantiles of the data to color by rank. Up to nine; they replace levels and maxValue. A day below the first threshold shows as empty, and a first threshold of 0 or less colors days of 0 too.
weekStartDefault the locale's, or Monday where the browser cannot tell
0123456
The first day of each week column, from 0 for Sunday to 6 for Saturday, as on Calendar.
toneDefault 'primary'
'neutral''primary''accent''success''warning''danger''done'
The tone the colors are mixed from: primary by default.
formatValueDefault the number format of EnvUIProvider's locale, or else the browser's
(value: number) => string
Text for a value.
formatDateDefault the weekday, day, month and year in the locale, such as Tue 14 Oct 2025
(date: string) => string
Text for a day's date, in its accessible name and the default tooltip.
renderTooltip
(day: CalendarHeatmapCell) => ReactNode
Tooltip content for a day; the date and value by default.
monthLabelsDefault the locale's short month names, from Intl
readonly string[]
Twelve month names above the weeks, from January.
weekdayLabelsDefault the locale's short weekday names, from Intl
readonly string[]
Seven weekday names beside the rows, from Sunday.
lessLabelDefault 'Less'
string
Legend text before the lightest color. It overrides EnvUIProvider's `messages.calendarHeatmap.less`.
moreLabelDefault 'More'
string
Legend text after the strongest color. It overrides EnvUIProvider's `messages.calendarHeatmap.more`.
aria-label
string
Required when aria-labelledby is not set. Names the grid of days, such as "Deployments in the last year"; this or aria-labelledby.
aria-labelledby
string
Required when aria-label is not set. The id of visible text that names the grid of days, such as a heading; this or aria-label.

Also accepts the native props of <div>, such as ref, className and aria-* attributes.

Part names

Each part's class and state attributes are public and versioned, for an application's CSS to style.

env-calendar-heatmap
The heatmap of days over months.
env-calendar-heatmap-corner
The empty corner above the weekday labels.
env-calendar-heatmap-day
One day's cell, filled by its level.
env-calendar-heatmap-grid
The grid of days.
env-calendar-heatmap-legend
The legend of levels.
env-calendar-heatmap-month
A month's label above the grid.
env-calendar-heatmap-pad
An empty cell outside the range.
env-calendar-heatmap-scroll
The region that scrolls the grid sideways.
env-calendar-heatmap-swatch
One level's swatch in the legend.
env-calendar-heatmap-tooltip
The tooltip of a day.
env-calendar-heatmap-tooltip-positioner
The tooltip's positioner.
env-calendar-heatmap-weekday
A weekday's label beside the grid.
data-closed
Any value, or present on env-calendar-heatmap-tooltip-positioner
data-date
Any value, or present on env-calendar-heatmap-day
data-day
Any value, or present on env-calendar-heatmap-day
data-level
Any value, or present on env-calendar-heatmap-day, env-calendar-heatmap-swatch
data-tone
accent · danger · done · neutral · primary · success · warning

Strings

EnvUIProvider's messages overrides each, and a component's own label prop wins over it.

messages.calendarHeatmap.less
"Less" The low end of the legend.
messages.calendarHeatmap.more
"More" The high end of the legend.

Changelog

1.0.0

formatTooltip is renderTooltip, as it returns content, as render* callbacks do; Weeks start on the locale's first day, as on Calendar, and weekStart takes any weekday; they started on Sunday. A date that doesn't exist, such as 2026-02-31, is left out and reported while developing, where it moved to the next month.

Month and weekday names, dates and numbers follow the locale, from Intl, and the legend comes from messages.calendarHeatmap; the tooltip is the shared tooltip popup. Heatmap is CalendarHeatmap, with CalendarHeatmapDay, CalendarHeatmapCell and the env-calendar-heatmap-* classes; the page is calendar-heatmap.