Heatmap

Show one value per pair of categories, with color carrying the magnitude.

const hours = /* ... */;
const days = /* ... */;
const chartData = /* ... */;

<HeatmapChart
  data={chartData}
  x="hour"
  y="day"
  value="sessions"
  label="Sessions"
  labelX="Hour"
  labelY="Day"
  ariaLabel="Sessions by weekday and hour"
/>

A heatmap answers where something concentrates. Two categorical axes place every cell exactly, and color carries the one value that belongs to that pair — the shape of a week, a season, or a service's bad month, read in one pass.

Installation

npx shadcn@latest add @dotui/chart-heatmap

It brings the chart core — the host, the palette, and the shared frame — along with it.

Usage

import { HeatmapChart } from '@/ui/chart-heatmap'

A heatmap reads long rows: one row per cell, with the column field, the row field, and the value. ariaLabel is required — a chart is a figure, not decoration.

const sessions = [
  { day: 'Mon', hour: '09', sessions: 32 },
  { day: 'Mon', hour: '10', sessions: 58 },
  { day: 'Tue', hour: '09', sessions: 30 },
]

export function Example() {
  return (
    <HeatmapChart
      data={sessions}
      x="hour"
      y="day"
      value="sessions"
      label="Sessions"
      ariaLabel="Sessions by weekday and hour"
    />
  )
}

Both axes are band scales, so the categories appear in the order the rows first mention them — sort your data, and the matrix follows. label names what the value means: it titles the legend and labels the value in the tooltip. labelX and labelY do the same for the two coordinates.

Color

Color is the whole reading, so it is binned rather than continuous: each bin is a distinct step, and the legend prints the boundaries between them. colors is the ramp, low to high, and its length is the number of bins.

import { heatmapColors, HeatmapChart } from '@/ui/chart-heatmap'
;<HeatmapChart
  data={sessions}
  x="hour"
  y="day"
  value="count"
  colors={heatmapColors('var(--chart-4)', 6)}
  ariaLabel="Sessions by hour and weekday"
/>

heatmapColors builds a ramp from a single palette slot: the low steps fade into the surface and the high steps toward the foreground, so the ramp stays sequential in light and dark without a second set of values. Any array of CSS colors works in its place.

Without thresholds, the bins split the observed extent evenly. Pass thresholds when the cuts are a policy rather than an extent — one incident is already worth seeing, ten is an outage week:

<HeatmapChart
  data={incidents}
  x="week"
  y="service"
  value="incidents"
  thresholds={[1, 4, 10]}
  colors={heatmapColors('var(--chart-4)', 4)}
  label="Incidents"
  ariaLabel="Incidents per service and week"
/>

There is always one more color than cut.

Values in cells

values writes each number inside its cell, and formatValue formats it — in the cells, the legend, and the tooltip at once. Each label picks black or white from the lightness of the cell under it, so it stays readable at both ends of the ramp.

<HeatmapChart
  data={adoption}
  x="quarter"
  y="region"
  value="share"
  values
  formatValue={{ locale: 'en-US', number: { style: 'percent' } }}
  ariaLabel="Feature adoption by region and quarter"
/>

Only turn it on when the cells are large enough to hold a number — a dozen cells, not a hundred. Everywhere else the tooltip is the place for exact figures.

Accessibility

The chart surface is focusable and keyboard-navigable: arrow keys move between cells, and each focused cell is announced with its two categories and its value. ariaLabel is required and names the figure; ariaDescription adds the longer explanation a sighted reader gets from the surrounding copy.

A heatmap encodes its measure in color alone, which is exactly the encoding a reader may not perceive. The legend is visual guidance and is hidden from assistive technology, so give the value a label, keep the tooltip on, and turn on values whenever the cells can hold the numbers.

Examples

Matrix

Calendar Months

Discrete Scale

With Values

API Reference

HeatmapChart

A matrix of cells: two categorical axes, and one numeric value per cell carried by color. Every interaction and host prop of `Chart` also applies — see `ChartBehaviorProps` and `ChartProps`.

PropType
readonly unknown[]
string
string
readonly string[]
readonly number[]
boolean
ChartFormat
string
string
string
boolean
boolean
ChartFormat
ChartFormat
string
readonly ChartMarkLayer[]
readonly ChartMarkLayer[]
ReactNode

Interaction and animation props are shared by every chart family:

Interaction and animation props shared by every chart family component. They are flat scalars on purpose: the chart definition is memoized on a serialized key, and a nested option object would silently go stale.

PropType
ChartFocus
number
ChartTooltipAnchor
boolean
false
ChartAnimate

The host props — height, width, className, and the focus callbacks — are documented on the Chart page.

Last updated on 8/1/2026