Heatmap
Encodes a labelled matrix with a sequential visualization scale.
← All chart guides · Framework setup · Native counterparts
See it and copy it
Framework example
Explore the live chart, then open its code for Astro, React, or Elements.
When teams do their best work
Active sessions · 21–27 September 2026 · UTC
55 available heatmap cells.
View chart data
| Column | Row | Value |
|---|---|---|
| 06:00 | Monday | 8 |
| 08:00 | Monday | 32 |
| 10:00 | Monday | 78 |
| 12:00 | Monday | 56 |
| 14:00 | Monday | 84 |
| 16:00 | Monday | 62 |
| 18:00 | Monday | 28 |
| 20:00 | Monday | 12 |
| 06:00 | Tuesday | 12 |
| 08:00 | Tuesday | 46 |
| 10:00 | Tuesday | 92 |
| 12:00 | Tuesday | 64 |
| 14:00 | Tuesday | 110 |
| 16:00 | Tuesday | 86 |
| 18:00 | Tuesday | 34 |
| 20:00 | Tuesday | 16 |
| 06:00 | Wednesday | 10 |
| 08:00 | Wednesday | 42 |
| 10:00 | Wednesday | 86 |
| 12:00 | Wednesday | 72 |
| 14:00 | Wednesday | 124 |
| 16:00 | Wednesday | 94 |
| 18:00 | Wednesday | 42 |
| 20:00 | Wednesday | 18 |
| 06:00 | Thursday | 14 |
| 08:00 | Thursday | 38 |
| 10:00 | Thursday | 82 |
| 12:00 | Thursday | 68 |
| 14:00 | Thursday | 102 |
| 16:00 | Thursday | 78 |
| 18:00 | Thursday | 36 |
| 20:00 | Thursday | 14 |
| 06:00 | Friday | 6 |
| 08:00 | Friday | 28 |
| 10:00 | Friday | 64 |
| 12:00 | Friday | 48 |
| 14:00 | Friday | 72 |
| 16:00 | Friday | 46 |
| 18:00 | Friday | 20 |
| 20:00 | Friday | 8 |
| 06:00 | Saturday | 0 |
| 08:00 | Saturday | 4 |
| 10:00 | Saturday | 18 |
| 12:00 | Saturday | 26 |
| 14:00 | Saturday | 22 |
| 16:00 | Saturday | 14 |
| 18:00 | Saturday | 8 |
| 20:00 | Saturday | 2 |
| 06:00 | Sunday | 2 |
| 08:00 | Sunday | 6 |
| 10:00 | Sunday | 14 |
| 12:00 | Sunday | 20 |
| 14:00 | Sunday | 18 |
| 16:00 | Sunday | 12 |
| 18:00 | Sunday | 6 |
| 20:00 | Sunday | Not available |
View Heatmap code
Framework usage
Switch targets to compare the adapter code. This live preview runs the Astro example. React and Elements use their own behavior APIs; complete the matching framework setup before copying an example.---
import { Heatmap } from '@santi020k/lumen-astro'
const hours = ['06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00']
const activity = [
{ day: 'Monday', sessions: [8, 32, 78, 56, 84, 62, 28, 12] },
{ day: 'Tuesday', sessions: [12, 46, 92, 64, 110, 86, 34, 16] },
{ day: 'Wednesday', sessions: [10, 42, 86, 72, 124, 94, 42, 18] },
{ day: 'Thursday', sessions: [14, 38, 82, 68, 102, 78, 36, 14] },
{ day: 'Friday', sessions: [6, 28, 64, 48, 72, 46, 20, 8] },
{ day: 'Saturday', sessions: [0, 4, 18, 26, 22, 14, 8, 2] },
{ day: 'Sunday', sessions: [2, 6, 14, 20, 18, 12, 6, null] }
]
const data = activity.flatMap(({ day, sessions }) => hours.map((hour, index) => ({
x: hour,
y: day,
value: sessions[index] ?? null
})))
---
<Heatmap
aria-label="Active sessions by weekday and time of day"
heading="When teams do their best work"
description="Active sessions · 21–27 September 2026 · UTC"
caption="Activity peaks on Wednesday at 14:00. Darker cells mean more sessions; × marks a missing observation. Illustrative data."
{data}
/>import { Heatmap } from '@santi020k/lumen-react'
const hours = ['06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00']
const activity = [
{ day: 'Monday', sessions: [8, 32, 78, 56, 84, 62, 28, 12] },
{ day: 'Tuesday', sessions: [12, 46, 92, 64, 110, 86, 34, 16] },
{ day: 'Wednesday', sessions: [10, 42, 86, 72, 124, 94, 42, 18] },
{ day: 'Thursday', sessions: [14, 38, 82, 68, 102, 78, 36, 14] },
{ day: 'Friday', sessions: [6, 28, 64, 48, 72, 46, 20, 8] },
{ day: 'Saturday', sessions: [0, 4, 18, 26, 22, 14, 8, 2] },
{ day: 'Sunday', sessions: [2, 6, 14, 20, 18, 12, 6, null] }
]
const data = activity.flatMap(({ day, sessions }) => hours.map((hour, index) => ({
x: hour,
y: day,
value: sessions[index] ?? null
})))
export const Example = () => (
<>
<Heatmap
aria-label="Active sessions by weekday and time of day"
heading="When teams do their best work"
description="Active sessions · 21–27 September 2026 · UTC"
caption="Activity peaks on Wednesday at 14:00. Darker cells mean more sessions; × marks a missing observation. Illustrative data."
data={data}
/>
</>
)<script type="module">
import { defineLumenElements } from '@santi020k/lumen-elements/define'
defineLumenElements(['Heatmap'])
</script>
<lumen-heatmap id="example-heatmap"
aria-label="Active sessions by weekday and time of day"
heading="When teams do their best work"
description="Active sessions · 21–27 September 2026 · UTC"
caption="Activity peaks on Wednesday at 14:00. Darker cells mean more sessions; × marks a missing observation. Illustrative data."></lumen-heatmap>
<script type="module">
"use strict";
const hours = ['06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00'];
const activity = [
{ day: 'Monday', sessions: [8, 32, 78, 56, 84, 62, 28, 12] },
{ day: 'Tuesday', sessions: [12, 46, 92, 64, 110, 86, 34, 16] },
{ day: 'Wednesday', sessions: [10, 42, 86, 72, 124, 94, 42, 18] },
{ day: 'Thursday', sessions: [14, 38, 82, 68, 102, 78, 36, 14] },
{ day: 'Friday', sessions: [6, 28, 64, 48, 72, 46, 20, 8] },
{ day: 'Saturday', sessions: [0, 4, 18, 26, 22, 14, 8, 2] },
{ day: 'Sunday', sessions: [2, 6, 14, 20, 18, 12, 6, null] }
];
const data = activity.flatMap(({ day, sessions }) => hours.map((hour, index) => ({
x: hour,
y: day,
value: sessions[index] ?? null
})));
const chart = document.getElementById('example-heatmap')
chart?.setAttribute('data', JSON.stringify(data))
</script>Framework differences
Elements: Pass data as a JSON attribute. The Elements example serializes the sample data with JSON.stringify; Astro expression bindings are not HTML attributes.
When to use Heatmap
Use for a matrix such as weekday by hour, region by product, or another pair of categorical dimensions.
- The scale legend shows domain endpoints and the diverging midpoint when applicable. Missing observations use a separate × marker.
- Keep the exact-data disclosure available: readers should not have to estimate values from color. Share the same domain across comparable matrices.
Prepare the data
import type { LumenHeatmapDatum } from '@santi020k/lumen-core'
const data = [
{ x: 'Morning', y: 'Monday', value: 18 },
{ x: 'Afternoon', y: 'Monday', value: 42 },
{ x: 'Morning', y: 'Tuesday', value: null },
{ x: 'Afternoon', y: 'Tuesday', value: 64 }
] satisfies readonly LumenHeatmapDatum[]- Each datum identifies a column with x, a row with y, and a finite value or null. Aggregate duplicate row/column pairs before plotting.
- Use xLabel and yLabel when readable labels differ from the stored identities. Keep numeric source values separate from formatValue output.
- Use a sequential colorScale for magnitude. Use "diverging" with an explicit meaningful midpoint for differences around a reference.
Avoid misleading comparisons
- Do not treat missing cells as zero activity. Supply explicit null observations where absence needs to remain visible.
- A heatmap is best for patterns, not small numerical differences. Use a table or bars when precise comparisons are the primary task.
Choose your target
Add this component
Choose the package for your runtime. All adapters share the Lumen stylesheet. Use the matching registry command when you want a local wrapper for that framework.
pnpm add @santi020k/lumen-astropnpm add @santi020k/lumen-astrolumen add Heatmaplumen add Heatmappnpm add @santi020k/lumen-reactpnpm add @santi020k/lumen-reactlumen add Heatmap --target reactlumen add Heatmap --target reactpnpm add @santi020k/lumen-elementspnpm add @santi020k/lumen-elementslumen add Heatmap --target elementslumen add Heatmap --target elementsAPI reference
Lumen-specific props and runtime attributes for the Astro primitive. Use the framework differences above and the copyable examples for React composition and Elements attributes; the public adapter types define their supported contracts.
| Attribute | Values | Default | Description |
|---|---|---|---|
| colorScale, midpoint | "sequential" | "diverging", number | "sequential", 0 | Uses semantic color scales with an explicit midpoint for diverging values. |
| showLegend | boolean | true | Shows scale endpoints and the distinct missing-data marker. |
| drilldown | boolean (Astro / Elements attribute) | false | Enables plotted datum actions and an equivalent native button disclosure, including with hidden tables. Astro requires UIPrimitives; Elements owns its lifecycle. |
| onDatumActivate | (detail: LumenChartDatumActivationDetail) => void (React) | - | Enables React datum actions and receives validated values with raw identities. Navigation and filtering remain application owned. |
| explore-data-label, datum-action-prefix, datumActionFormatter | string attributes, (context: string) => string property (Elements) | English labels | Localizes the Elements disclosure and datum actions. |
| labels.exploreData, labels.formatDatumAction | string, (context: string) => string | English labels | Localizes the actions disclosure and each contextual datum button. |
| data | LumenHeatmapDatum[] | [] | Provides labelled row, column, and intensity values for the heatmap cells. |
| formatValue | (value: number) => string | String | Formats finite cell values in titles and the accessible data table. |
| showTable | boolean | true | Provides a revealable semantic table for every heatmap cell. |
| class, className | string | "" | Merges custom classes with the generated ui-* root classes. |
| ...native attributes | HTML attributes | - | Forwards standard attributes to the root element unless the component consumes them. |
Keyboard interactions
Keyboard behavior provided by the Astro UIPrimitives runtime for this component.
| Key | Action |
|---|---|
| Tab | Focus the actions disclosure and its native buttons in document order. |
| Enter, Space | Toggle the disclosure or activate the focused datum button. Actions remain available when the data table is hidden. |
Runtime events
CustomEvents dispatched by the Astro UIPrimitives runtime for this component.
| Event | Target | Detail | When |
|---|---|---|---|
| ui:chart-datum-activate | Astro chart figure or Elements data chart host | LumenChartDatumActivationDetail: series { seriesId, x, y, datumId? }, heatmap { x, y, value, datumId? }, range { x, low, high, datumId? } | Fires once after a plotted datum or its native action button is activated. Requires drilldown. Astro uses UIPrimitives, Elements owns its lifecycle, and React uses onDatumActivate instead. |