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-heatmapIt 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
ariaLabelis required and names the figure; addariaDescriptionwhen the takeaway needs a sentence.- The chart surface is in the tab order. Arrow keys move between cells,
HomeandEndjump to the first and last,Enterand Space pin the tooltip, andEscapedismisses 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
labeland turn onvalueswhenever 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.
| Prop | Type | Default | |
|---|---|---|---|
readonly unknown[] | — | ||
string | — | ||
string | — | ||
string | — | ||
readonly string[] | heatmapColors() | ||
readonly number[] | — | ||
boolean | false | ||
ChartFormat | — | ||
string | — | ||
string | — | ||
boolean | true | ||
boolean | true | ||
ChartFormat | — | ||
ChartFormat | — | ||
readonly ChartMarkLayer[] | — | ||
readonly ChartMarkLayer[] | — | ||
union | "nearest" | ||
'point' | 'pointer' | 'group-center' | "point" | ||
string | — | ||
string | — | ||
number | — | ||
boolean | true | ||
boolean | true | ||
ChartAnimate | { type: "spring", stiffness: 170, damping: 26 } | ||
number | 256 | ||
number | — | ||
number | — | ||
number | — | ||
string | — | ||
ReactNode | function | — | ||
ReactNode | — | ||
Last updated on 9/7/2026