HeatMap
Two-dimensional matrix with color intensity. Day×hour usage, correlation, activity grids.
import { HeatMap } from "metricui";Use HeatMap when you need to show patterns across two categorical axes — day/hour activity grids, server response time matrices, or correlation tables. For single-axis trends over time, use AreaChart or BarChart instead.
Basic Example
<HeatMap
data={[
{ id: "Mon", data: [{ x: "9am", y: 12 }, { x: "10am", y: 45 }, { x: "11am", y: 78 }] },
{ id: "Tue", data: [{ x: "9am", y: 23 }, { x: "10am", y: 56 }, { x: "11am", y: 92 }] },
{ id: "Wed", data: [{ x: "9am", y: 18 }, { x: "10am", y: 62 }, { x: "11am", y: 85 }] },
]}
title="Weekly Activity"
/>Labels & Formatting
Enable enableLabels to show formatted values inside each cell. Combine with format for number formatting and borderRadius for rounded cells.
Sessions by day and hour
<HeatMap
data={activityData}
title="User Activity"
subtitle="Sessions by day and hour"
format="number"
enableLabels
borderRadius={6}
height={320}
/>Diverging Color Scale
Switch to colorScale="diverging" for data with a meaningful midpoint (like correlations from -1 to 1). Combine with forceSquare for symmetric matrices.
<HeatMap
data={correlationData}
title="Metric Correlations"
colorScale="diverging"
enableLabels
forceSquare
/>Simple Data Shorthand
Use simpleData for a flat object format that gets converted to the series format automatically.
// Instead of the full series format, use simpleData:
<HeatMap
simpleData={{
Mon: { "9am": 12, "10am": 45, "11am": 78 },
Tue: { "9am": 23, "10am": 56, "11am": 92 },
}}
title="Weekly Activity"
/>Data States
Every component handles loading, empty, and error states.
Loading
Error
Failed to load data
// Loading state
<HeatMap data={[]} title="Activity" loading />
// Error state
<HeatMap data={[]} title="Activity" error={{ message: "Failed to load" }} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
data* | { id: string; data: { x: string; y: number | null }[] }[] | — | Array of series. Each series is a row, each datum is a column cell. |
index | string | — | Column key for row IDs. Used with the unified flat-row data format. If omitted with categories, auto-inferred as the first string column. |
categories | Category[] | — | Columns to use as cell columns. Accepts plain strings or CategoryConfig objects ({ key, label?, format?, color?, axis? }). If omitted with index, auto-inferred as all number columns. |
simpleData | Record<string, Record<string, number | null>> | — | Simple 2D object format. Converted to series internally. data takes precedence. |
title | string | — | Card title. |
subtitle | string | — | Card subtitle. |
description | string | React.ReactNode | — | Popover description. |
footnote | string | — | Footnote below the chart. |
action | React.ReactNode | — | Action slot in the header. |
format | FormatOption | — | Format for cell values and tooltips. |
height | number | 300 | Chart height in px. |
colorScale | "sequential" | "diverging" | "sequential" | Color scale type. |
colors | string[] | — | Custom color stops. Overrides colorScale. |
emptyColor | string | — | Color for null/missing cells. |
borderRadius | number | 4 | Cell corner radius. |
enableLabels | boolean | false | Show values inside cells. |
forceSquare | boolean | false | Force cells to be square. |
cellPadding | number | 0.05 | Inner padding between cells (0-1). |
hoverTarget | "cell" | "row" | "column" | "rowColumn" | "cell" | What to highlight on hover. 'cell' = single cell, 'row' = entire row, 'column' = entire column, 'rowColumn' = cross-hair highlighting both row and column. Use 'rowColumn' for comparing patterns across dimensions. |
onCellClick | (cell: { serieId: string; x: string; value: number | null }) => void | — | Click handler for cells. |
animate | boolean | — | Enable animation. Reads from MetricProvider. |
variant | CardVariant | — | Card variant (supports custom strings). CSS-variable-driven via [data-variant]. Reads from MetricProvider. |
dense | boolean | — | Dense mode. Reads from MetricProvider. |
className | string | — | Additional class names. |
classNames | { root?: string; header?: string; chart?: string } | — | Sub-element class overrides. |
id | string | — | HTML id. |
data-testid | string | — | Test id. |
loading | boolean | — | Show skeleton. |
empty | EmptyState | — | Empty state. |
error | ErrorState | — | Error state. |
stale | StaleState | — | Stale indicator. |
crossFilter | boolean | { field: string } | — | Enable cross-filter signal on click. true uses the row id field, { field } overrides. Emits selection via CrossFilterProvider — does NOT change chart appearance. Dev reads selection with useCrossFilter() and filters their own data. |
tooltipHint | boolean | string | — | Show action hint at bottom of tooltip. true: auto ('Click to drill down' / 'Click to filter'). string: custom hint text. false: disable. Default: respects MetricConfig.tooltipHint (true). |
drillDown | boolean | ((event: { serieId: string; x: string; y: number }) => React.ReactNode) | — | Enable drill-down on cell click. `true` auto-generates a summary KPI row + filtered DataTable from the chart's source data. Pass a render function for full control over the panel content. Requires DrillDown.Root wrapper. When both drillDown and crossFilter are set, drillDown wins. |
drillDownMode | DrillDownMode | "slide-over" | Presentation mode for the drill-down panel. "slide-over" (default) slides from the right, full height. "modal" renders centered and compact. |
Data Shape
// Each series is a row, each datum is a column
type HeatMapSeries = {
id: string; // Row label (e.g. "Monday")
data: {
x: string; // Column label (e.g. "9am")
y: number | null; // Cell value
}[];
};
// Or use simpleData shorthand:
const simpleData = {
Mon: { "9am": 12, "10am": 45, "11am": 78 },
Tue: { "9am": 23, "10am": 56, "11am": 92 },
};Notes
- Uses forwardRef — attach a ref to the root div.
- Uses ChartContainer for consistent card styling.
- simpleData shorthand converts { row: { col: value } } to the series format automatically.
- Sequential color scale uses blues scheme. Diverging uses red-yellow-green.
- Custom colors array overrides the preset color scales.
- crossFilter prop emits a selection signal on click — it does NOT change the chart's appearance. The dev reads the selection via useCrossFilter() and filters their own data.
- drillDown={true} auto-generates a summary KPI row + filtered DataTable from the chart's source data. Pass a render function for custom panel content. Requires DrillDown.Root wrapper.
- When both drillDown and crossFilter are set on the same component, drillDown wins.
Playground
Experiment with every prop interactively. Adjust the controls on the right to see the component update in real time.
Live Preview
User sessions by day and hour
Code
<HeatMap
data={weeklyActivityData}
title="Weekly Activity"
subtitle="User sessions by day and hour"
/>Props
Adjust props to see the chart update in real time
Switch between sample datasets
Cell corner radius in px
Inner padding between cells (0-1 ratio)
sequential = single hue, diverging = two-tone with midpoint