Heatmap calendar grid component for PhiaUI.
Renders a GitHub-style contribution heatmap — a 2-D grid where each cell's colour intensity represents the magnitude of a metric at that position.
CSS-only — no JS hook required. Fully server-rendered.
When to use
Common use cases for heatmap grids:
- GitHub contribution graph — activity over 52 weeks × 7 days
- Site traffic heatmap — hour-of-day vs. day-of-week (24 × 7)
- Server load matrix — CPU/memory over time buckets
- Sales frequency grid — products vs. months
- Error rate map — services vs. deployment environments
Intensity levels
Values are bucketed into 5 levels (0–4) relative to max_value.
Add the following CSS to your stylesheet or priv/static/theme.css to apply
colours (these use the primary token so they respect the active theme):
.heatmap-0 { background-color: oklch(var(--muted)); }
.heatmap-1 { background-color: oklch(var(--primary) / 0.2); }
.heatmap-2 { background-color: oklch(var(--primary) / 0.4); }
.heatmap-3 { background-color: oklch(var(--primary) / 0.65); }
.heatmap-4 { background-color: oklch(var(--primary) / 0.9); }Data format
Pass a flat list of %{col: integer, row: integer, value: integer} maps.
Positions not present in data are treated as value 0 (level 0, empty).
Example — GitHub-style contribution heatmap
# Build 52 weeks × 7 days of contribution data from the database:
data =
Contributions.last_year(user_id)
|> Enum.map(fn c -> %{col: c.week, row: c.day_of_week, value: c.count} end)
<.heatmap_calendar
data={data}
rows={7}
cols={52}
row_labels={~w[Sun Mon Tue Wed Thu Fri Sat]}
max_value={10}
show_legend={true}
/>Example — hourly traffic heatmap (24 hours × 7 days)
<.heatmap_calendar
data={@traffic_data}
rows={7}
cols={24}
row_labels={~w[Mon Tue Wed Thu Fri Sat Sun]}
col_labels={~w[0h 3h 6h 9h 12h 15h 18h 21h]}
max_value={@peak_traffic}
show_legend={true}
/>Accessibility
The grid uses role="grid" on each row container. Each cell uses
role="gridcell" and an aria-label describing its position and raw value
so keyboard users and screen readers can navigate the matrix.
Summary
Functions
Renders a heatmap calendar grid.
Functions
Renders a heatmap calendar grid.
Builds a {col, row} => value lookup map from the data list at render time,
then iterates rows × cols to produce a WAI-ARIA grid. Each cell receives
a heatmap-{0..4} CSS class derived from the bucketed intensity.
The outer overflow-x-auto wrapper allows wide grids (e.g. 52 columns) to
scroll horizontally on narrow screens without breaking the page layout.
Attributes
data(:list) - List of%{col: integer, row: integer, value: integer}maps.colandroware 0-based indices. Positions absent from the list are treated asvalue: 0(rendered at intensity level 0).Defaults to
[].rows(:integer) (required) - Number of rows in the grid (e.g.7for days of the week).cols(:integer) (required) - Number of columns in the grid (e.g.52for weeks in a year,24for hours).max_value(:integer) - Maximum expected value used to normalise raw values into intensity buckets 0–4. Values at or abovemax_valueare clamped to level 4. Set this to your 95th-percentile value to avoid a single outlier washing out the rest.Defaults to
10.col_labels(:list) - Optional list of column header strings. For readability, pass a subset — not every column needs a label. Length need not matchcols. Example:~w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec]for monthly buckets.Defaults to
nil.row_labels(:list) - Optional list of row label strings. Length should matchrows. Example:~w[Sun Mon Tue Wed Thu Fri Sat]for day-of-week rows.Defaults to
nil.show_legend(:boolean) - Whentrue, renders a small "Less → More" intensity legend below the grid. Recommended when the colour scale is not self-evident from context.Defaults to
false.class(:string) - Additional CSS classes for the root wrapper. Defaults tonil.Global attributes are accepted. HTML attributes forwarded to the root div.