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-heatmapIt 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`.
| Prop | Type | Default | |
|---|---|---|---|
readonly unknown[] | — | ||
string | — | ||
string | — | ||
readonly string[] | heatmapColors() | ||
readonly number[] | — | ||
boolean | false | ||
ChartFormat | — | ||
string | — | ||
string | — | ||
string | — | ||
boolean | true | ||
boolean | true | ||
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.
| Prop | Type | Default | |
|---|---|---|---|
ChartFocus | "group-x" | ||
number | — | ||
ChartTooltipAnchor | "group-center" | ||
boolean | true | ||
false | — | ||
ChartAnimate | { duration: 240, respectReducedMotion: true } |
The host props — height, width, className, and the focus callbacks — are documented on the Chart page.
Last updated on 8/1/2026