Calendar Heatmap
A calendar heatmap shows one value per day. Columns are weeks, rows are weekdays, and the color gets stronger with the value, like the contribution graph on a GitHub profile.
Contributions
Monday first, own colors, other locale
week-start moves the first row to Monday. colors sets every level from empty to full; max sets the value that reaches the top color. locale changes month, weekday and tooltip text.
Days and time zones
A day is a calendar date, not a moment in time. date-key values are either YYYY-MM-DD strings or Date objects; a Date counts by its local calendar date, so a local midnight never moves to the day before. Rows with the same day are added up. Invalid dates such as 2026-02-30 are skipped.
Without end, the calendar ends on the latest day in data, not on today, so the server and the browser render the same days. Without start, it shows the 52 weeks before end.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | Row[] | required | Rows with a day and a value |
date-key | row path or accessor | 'date' | Field with the day |
data-key | row path or accessor | 'value' | Field with the number to color by |
start / end | string | Date | 52 weeks up to the latest day | Range shown |
week-start | 0–6 | 0 (Sunday) | First weekday row |
color | string | var(--v-charts-series-1, var(--v-charts-series, #2563eb)) | Full-intensity color |
empty-color | string | var(--v-charts-muted, #ebedf0) | Color for no value and zero |
levels | number | 4 | Steps above empty; each mixes color into empty-color |
colors | string[] | — | Explicit fill per level, from empty to full |
max | number | largest value in range | Value that reaches the top level |
gap | number | 3 | Space between squares in px |
radius | number | 2 | Corner radius |
locale | string | 'en-US' | Locale for month, weekday and date text, and for numbers |
value-formatter | (value, day) => string | locale number format (1,234) | Value text in the tooltip and accessible names. String keeps raw numbers |
month-labels / weekday-labels | boolean | true | Show the labels |
width / height / aspect | number | full width, square cells | Size. See chart size |
title | string | 'Activity calendar' | Accessible name |
active-index | number | null | — | Rendered cell position, for v-model:active-index |
is-animation-active / transition | true / motion tokens | Animate data changes |
Events and slots
| Payload | Description | |
|---|---|---|
@cell-click, @cell-mouseenter, @cell-mouseleave | (day: CalendarDay<Row>, index, event) | The day behind the square. cell-click gets a MouseEvent, or a KeyboardEvent for Enter |
#cell | { cell, index, active, x, y, width, height, fill, radius } | Replace the square's <rect> |
| default slot | — | Place <Tooltip> here |
Keyboard
Arrow keys move by day (up and down) and by week (left and right), skipping days outside the range. Home and End jump to the first and last day, Enter emits cell-click for the active day (with a KeyboardEvent), Escape clears the selection.
Bind v-model:active-index to control selection; null clears it. Pointer and keyboard changes request an update. See Events and v-model for control precedence.
The row type Row is inferred from data. Key props accept typed row paths or accessors.
CalendarDay<Row>.rows contains the source rows for that day. Missing days have an empty
rows array. Events, the cell slot and tooltip payloads use the same day object.