Heatmap

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

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

<HeatmapChart
  data={data}
  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

pnpm dlx 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 "@/components/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.

Cells tile the plot, so the heatmap has no grid prop — the cell edges are the grid.

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 "@/components/ui/chart-heatmap"
<HeatmapChart
  data={sessions}
  x="hour"
  y="day"
  value="sessions"
  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

  • ariaLabel is required and names the figure; add ariaDescription when the takeaway needs a sentence.
  • The chart surface is in the tab order. Arrow keys move between cells, Home and End jump to the first and last, Enter and Space pin the tooltip, and Escape dismisses it.
  • 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 and turn on values whenever the cells can hold the numbers.
  • tooltip={false} keeps the focus stops but removes their live region, so screen readers land on silent points. Turn it off only for a decorative chart — and never on a heatmap, where the tooltip is the only place the exact value is spoken.

Examples

Matrix

Calendar Months

Discrete Scale

With Values

API Reference

HeatmapChart

Heatmap chart. Give it rows plus the fields to read: one row per cell, with the column field, the row field, and the numeric value color carries.

PropType
readonly unknown[]
string
string
string
readonly string[]
readonly number[]
boolean
ChartFormat
string
string
boolean
boolean
ChartFormat
ChartFormat
readonly ChartMarkLayer[]
readonly ChartMarkLayer[]
union
'point' | 'pointer' | 'group-center'
string
string
number
boolean
boolean
ChartAnimate
number
number
number
number
string
ReactNode | function
ReactNode

Last updated on 9/7/2026