Heatmap Cell Animation
Day-by-day heat calendar: each cell takes one of five intensities from its value, and the cells colour in one after another, in a wave.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Data has 28 effects, including 1 free. Explore the category →
3 usage examples



How it works
You give one value per day in data-values (comma-separated numbers); the script builds a real seven-column <table> with one row per week. Day headers come from the page language through Intl.DateTimeFormat ("M T W T F S S" in English, "L M M J V S D" in French), starting on Monday or on the day set by data-week-start.
Each .hmp-cell gets a level from 0 to 4 in data-level: 0 for a zero value, then 1 to 4 depending on which quarter of the maximum it falls in (the data maximum, or data-max). The CSS paints the levels with one colour at 5, 20, 40, 65 and 100% opacity, with a glow on the two strongest; the "Less / More" legend uses exactly these five shades.
On first appearance and on every data-values change (watched by a MutationObserver), cells change shade over 0.8 s, 25 ms apart from one to the next: colour spreads like a wave from the first week to the last. On hover, a cell grows by 20%.
The last week can be incomplete: missing days stay as empty cells. Each root builds its own grid, so several calendars can share a page.
Accessibility
- A readable table: a screen reader finds a table named by
data-label(in a<caption>), with full day names as headers and, in each cell, hidden text such as "week 2: 290 visitors". - Exact values, not just shades: colour levels are only a summary; the precise value (with
data-unit) is in each cell's text and in itsdata-valueattribute. - Reduced motion: under
prefers-reduced-motion: reducethe wave is turned off and every cell takes its shade at once. - Hover hides nothing: the hover growth is a visual cue; it reveals nothing that is not already in the table. If you add a tooltip, read the value from
data-valueand provide keyboard access too.
Browser compatibility
HTML table, aspect-ratio, colour transitions, Intl.DateTimeFormat, MutationObserver, IntersectionObserver. Measured in Chromium 151, Firefox 153 and WebKit 26.5: cell mid-change at 150 ms, two independent calendars (the second one is no longer empty), 390 px, white background, no JS error.
Without JavaScript only the legend shows: the grid is built by the script. Add a numeric summary next to it if the calendar must stay readable without script.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0250-root" data-values="3,1,0,4,2,0,1,2,4,3,1,0,2,0,4,4,2,3,1,1,0,1,3,4,2,0,3,2" data-label="Activity over four weeks">
<div class="hmp-wrap">
<table class="hmp-grid"></table>
<div class="hmp-scale" aria-hidden="true">
<span class="hmp-less">Less</span>
<span class="hmp-swatch" data-level="0"></span>
<span class="hmp-swatch" data-level="1"></span>
<span class="hmp-swatch" data-level="2"></span>
<span class="hmp-swatch" data-level="3"></span>
<span class="hmp-swatch" data-level="4"></span>
<span class="hmp-more">More</span>
</div>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
data-values (HTML, on the root) |
28 values from 0 to 4 | One value per day, in order, starting on the first day of the week. The number of weeks follows the number of values. |
data-max (HTML) |
the largest value | Value matching the strongest level; useful to compare several months on one scale. |
data-week-start (HTML) |
1 (Monday) | First day of each row: 0 for Sunday, 1 for Monday… 6 for Saturday. |
data-unit, data-decimals, data-label (HTML) |
none, 0, "Activity over four weeks" | Unit and decimals of the text read in each cell, table name. |
--hmp-color (CSS) |
99, 102, 241 | Cell and legend colour, as three R, G, B numbers (level opacities are applied on top). |
--hmp-gap, --hmp-radius, --hmp-max-width (CSS) |
4px, 4px, 320px | Space between cells, corner radius, maximum grid width (cells stay square). |
--hmp-stagger, --hmp-speed (CSS) |
25ms, .8s | Delay between two cells and duration of the shade change. |
| Legend labels (HTML) | "Less", "More" | Text of .hmp-less and .hmp-more, to match your data. |
--hmp-bg, --hmp-text, --hmp-min-height (CSS) |
#0a0a0f, 40% white, 300px | Background, colour of day letters and legend, minimum height. |
FAQ
root.setAttribute('data-values', monthValues.join(',')). The grid is rebuilt if the number of values changes, then the wave recolours the cells. To compare two months, set data-max to the same value for both.data-week-start). Two options: start the series on the month's first Monday, as in the library example, or put 0 for the days before; those cells will then take the palest shade, the one for a zero value.data-value. Listen to pointerover on the root, find the cell with e.target.closest('.hmp-cell') and display its value wherever you like; that is what the library example does. The text read by screen readers is already in the cell.