Heatmap
A 2D intensity grid (the activity-calendar / punch-card shape), colored on a theme-derived ramp, with a per-cell readout and opt-in clickable cells.
Live demo
Heatmap renders a matrix of values as a grid of colored cells, each cell's fill scaled by its value on an intensity ramp resolved off the live theme, the activity-calendar / punch-card shape (runs per hour over a week, load by host and resource, a GitHub-style contribution grid). Feed it a dense values matrix (row-major) plus optional rows and cols labels; the default accent ramp washes from a faint surface up to the accent, and thermal or status (or an explicit color array) swap the scale.
Set categorical to color by category instead of intensity: each column (or each row, with categoryAxis="row") takes a distinct hue from a categorical SeriesScheme (accents / skittles / statuses), and the cell value washes that hue from the surface up. One grid, then, reads its categories by hue and their magnitude by fill: the categorical heat-grid. Turn scale on and a legend keys the hues. Every cell's intensity is normalized against the data's own maximum, or an explicit max so several grids share one scale. It's interactive: hovering or focusing a cell floats a value readout, and the whole grid is mirrored into a visually-hidden data table so assistive tech reads the numbers, not the pixels. Set selectable to make cells a drill-in surface, each firing a select event with its row, column, and value. showValues prints the number in each cell. Feed an optional second glow matrix (the same shape as values) to carry a second signal in one grid: the fill reads one metric while a per-cell halo reads another (run count vs total runtime, requests vs error rate). An empty matrix shows a muted No data message in place of the grid.
When to use
How this component composes with the rest of the set.
Props
20 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
States
cell-hover
Pointer over or keyboard focus on a cell: its border brightens (the rest are never dimmed, so a dense grid never strobes) and a value readout floats above it.
cell-focus
Keyboard focus on a cell draws a token ring; each cell is a tab stop that announces its row, column, and value.
current
A cell flagged via current: a thicker accent (or toned) ring marks it as "now", pulsing when currentPulse is set (and reduced-motion is not requested).
Anatomy
The named parts that make up the component, with their selectors.
chart
The <figure> root holding the SVG grid, tooltip, and the accessible data table.
cell
A single grid cell, filled by its value's intensity color; the focus/hover target.
current-cell
A cell marked as the current / "now" position: ringed in the accent (or a chosen tone), optionally pulsing.
tooltip
The floating value readout shown on hover or focus of a cell.
scale
The optional color-scale key below the grid: five ramp swatches between the low and high value endpoints.
legend
The categorical key below the grid: one swatch per category hue, labeled off the category axis.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xtyle-default register
(derive(xtyleDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent
--bg-0
--border-thick
--border-thin
--danger
--duration-fast
--ease-standard
--elevation-3
--fg-0
--fg-2
--fg-3
--font-sans
--info
--leading-normal
--neutral
--radius-md
--ring
--space-1
--space-2
--space-3
--success
--surface-overlay
--surface-overlay-border
--text-sm
--text-xs
--warn
--weight-semibold
Accessibility
Code
Activity grid
A week-by-hour activity grid on the default accent ramp, the contribution-graph shape.
<xtyle-heatmap
rows='["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]'
scale
label="Activity by day and hour"></xtyle-heatmap>
<script>
const grid = document.querySelector("xtyle-heatmap");
grid.cols = Array.from({ length: 24 }, (_, h) => String(h));
grid.values = weekByHour; // number[7][24]
</script>
<script lang="ts">
import { Heatmap } from "@xtyle/svelte";
const rows = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
const cols = Array.from({ length: 24 }, (_, h) => String(h));
const values = weekByHour; // number[7][24]
</script>
<Heatmap {rows} {cols} {values} scale label="Activity by day and hour" />
---
import { Heatmap } from "@xtyle/astro";
const rows = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
const cols = Array.from({ length: 24 }, (_, h) => String(h));
const values = weekByHour; // number[7][24]
---
<Heatmap rows={rows} cols={cols} values={values} scale label="Activity by day and hour" />
Thermal scale with values
scheme="thermal" runs the cells cool to hot, and showValues prints each number in its cell.
<xtyle-heatmap
scheme="thermal"
show-values
label="Load by host and resource"></xtyle-heatmap>
<script>
const grid = document.querySelector("xtyle-heatmap");
grid.rows = ["web-1", "web-2", "db-1"];
grid.cols = ["CPU", "Mem", "Net", "Disk"];
grid.values = [[42, 30, 18, 9], [26, 55, 22, 14], [12, 40, 8, 61]];
</script>
<script lang="ts">
import { Heatmap } from "@xtyle/svelte";
const rows = ["web-1", "web-2", "db-1"];
const cols = ["CPU", "Mem", "Net", "Disk"];
const values = [[42, 30, 18, 9], [26, 55, 22, 14], [12, 40, 8, 61]];
</script>
<Heatmap {rows} {cols} {values} scheme="thermal" showValues label="Load by host and resource" />
---
import { Heatmap } from "@xtyle/astro";
const rows = ["web-1", "web-2", "db-1"];
const cols = ["CPU", "Mem", "Net", "Disk"];
const values = [[42, 30, 18, 9], [26, 55, 22, 14], [12, 40, 8, 61]];
---
<Heatmap rows={rows} cols={cols} values={values} scheme="thermal" showValues label="Load by host and resource" />
Selectable cells
selectable makes each cell a button that fires select on click or Enter/Space, so a grid can drive a drill-in. The detail carries the row, column, value, and both indices.
<xtyle-heatmap
scheme="accent"
selectable
label="Runs per hour"></xtyle-heatmap>
<script>
const grid = document.querySelector("xtyle-heatmap");
grid.rows = ["Mon", "Tue", "Wed"];
grid.cols = ["AM", "PM", "Night"];
grid.values = [[3, 8, 1], [5, 2, 0], [0, 6, 4]];
grid.addEventListener("select", (e) => {
// { row, col, value, rowIndex, colIndex }
openRuns(e.detail.row, e.detail.col);
});
</script>
<script lang="ts">
import { Heatmap } from "@xtyle/svelte";
const rows = ["Mon", "Tue", "Wed"];
const cols = ["AM", "PM", "Night"];
const values = [[3, 8, 1], [5, 2, 0], [0, 6, 4]];
</script>
<Heatmap
{rows}
{cols}
{values}
scheme="accent"
selectable
label="Runs per hour"
onselect={(e) => openRuns(e.detail.row, e.detail.col)}
/>
---
import { Heatmap } from "@xtyle/astro";
const rows = ["Mon", "Tue", "Wed"];
const cols = ["AM", "PM", "Night"];
const values = [[3, 8, 1], [5, 2, 0], [0, 6, 4]];
---
<Heatmap rows={rows} cols={cols} values={values} scheme="accent" selectable label="Runs per hour" />
<script>
document.querySelector("xtyle-heatmap").addEventListener("select", (e) => {
openRuns(e.detail.row, e.detail.col);
});
</script>
Two-channel glow
A second glow matrix carries a second metric as a per-cell halo, so the fill reads run count while the glow reads total runtime, two signals in one grid.
<xtyle-heatmap
scheme="accent"
glow-label="runtime"
label="Runs (fill) and total runtime (glow) by day and bucket"></xtyle-heatmap>
<script>
const grid = document.querySelector("xtyle-heatmap");
grid.rows = ["Mon", "Tue", "Wed"];
grid.cols = ["AM", "PM", "Night"];
grid.values = [[3, 8, 1], [5, 2, 0], [0, 6, 4]];
grid.glow = [[40, 12, 90], [8, 30, 0], [0, 15, 70]];
</script>
<script lang="ts">
import { Heatmap } from "@xtyle/svelte";
const rows = ["Mon", "Tue", "Wed"];
const cols = ["AM", "PM", "Night"];
const values = [[3, 8, 1], [5, 2, 0], [0, 6, 4]];
const glow = [[40, 12, 90], [8, 30, 0], [0, 15, 70]];
</script>
<Heatmap {rows} {cols} {values} {glow} glowLabel="runtime" scheme="accent" label="Runs (fill) and total runtime (glow)" />
---
import { Heatmap } from "@xtyle/astro";
const rows = ["Mon", "Tue", "Wed"];
const cols = ["AM", "PM", "Night"];
const values = [[3, 8, 1], [5, 2, 0], [0, 6, 4]];
const glow = [[40, 12, 90], [8, 30, 0], [0, 15, 70]];
---
<Heatmap rows={rows} cols={cols} values={values} glow={glow} glowLabel="runtime" scheme="accent" label="Runs (fill) and total runtime (glow)" />
Current-cell marker and per-cell titles
current rings the live cell (with currentPulse to breathe it and currentTone to tint it), the "you are here" on a time grid, and titles gives a cell a fuller hover readout that doubles as its accessible name.
<xtyle-heatmap
rows='["Mon","Tue","Wed"]'
current-pulse
label="Runs per bucket, this week"></xtyle-heatmap>
<script>
const grid = document.querySelector("xtyle-heatmap");
grid.cols = ["AM", "PM", "Night"];
grid.values = [[3, 8, 1], [5, 2, 0], [0, 6, 4]];
// ring + pulse the live cell (Wed / PM), and give it a fuller hover readout
grid.current = [[2, 1]];
grid.titles = [[], [], [, "Wed PM: 6 runs · 2 failed · click to view"]];
</script>
<script lang="ts">
import { Heatmap } from "@xtyle/svelte";
const rows = ["Mon", "Tue", "Wed"];
const cols = ["AM", "PM", "Night"];
const values = [[3, 8, 1], [5, 2, 0], [0, 6, 4]];
const current = [[2, 1]];
const titles = [[], [], [, "Wed PM: 6 runs · 2 failed · click to view"]];
</script>
<Heatmap {rows} {cols} {values} {current} {titles} currentPulse label="Runs per bucket, this week" />
---
import { Heatmap } from "@xtyle/astro";
const rows = ["Mon", "Tue", "Wed"];
const cols = ["AM", "PM", "Night"];
const values = [[3, 8, 1], [5, 2, 0], [0, 6, 4]];
const current = [[2, 1]];
const titles = [[], [], [, "Wed PM: 6 runs · 2 failed · click to view"]];
---
<Heatmap rows={rows} cols={cols} values={values} current={current} titles={titles} currentPulse label="Runs per bucket, this week" />
Categorical heat-grid
categorical colors each column a distinct hue from a SeriesScheme (skittles here) and washes it up by value, so a grid whose columns are unrelated categories reads by hue, not one shared ramp. scale keys the hues in a legend.
<xtyle-heatmap
scheme="skittles"
categorical
scale
label="Weekly totals by category"></xtyle-heatmap>
<script>
const grid = document.querySelector("xtyle-heatmap");
grid.rows = ["Week 1", "Week 2", "Week 3"];
grid.cols = ["Bugs", "Features", "Docs", "Chores"];
grid.values = [[8, 3, 1, 5], [2, 6, 4, 3], [5, 1, 7, 2]];
</script>
<script lang="ts">
import { Heatmap } from "@xtyle/svelte";
const rows = ["Week 1", "Week 2", "Week 3"];
const cols = ["Bugs", "Features", "Docs", "Chores"];
const values = [[8, 3, 1, 5], [2, 6, 4, 3], [5, 1, 7, 2]];
</script>
<Heatmap {rows} {cols} {values} scheme="skittles" categorical scale label="Weekly totals by category" />
---
import { Heatmap } from "@xtyle/astro";
const rows = ["Week 1", "Week 2", "Week 3"];
const cols = ["Bugs", "Features", "Docs", "Chores"];
const values = [[8, 3, 1, 5], [2, 6, 4, 3], [5, 1, 7, 2]];
---
<Heatmap rows={rows} cols={cols} values={values} scheme="skittles" categorical scale label="Weekly totals by category" />