Data✨ Premium

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.

CSSGridHeatmap

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

University library attendance over four weeks — Heatmap Cell Animation example 1

① University library attendance over four weeks

WhenThe "Coming to the library" page: the big March grid on the left, on the right a panel showing the hovered day and its visitor count, a tip and opening hours. The hovered cell grows; the scene reads its value from the data-value attribute the effect sets.
WhyStudents look for the right time to come: the grid shows quiet weekends and the build-up to exams at a glance, and hovering gives the exact figure.
Settingsdata-unit="visitors", 28 values from Monday 2 to Sunday 29, --hmp-color:129,140,248 (light indigo), --hmp-gap:5px, --hmp-max-width:none, "Quiet / Busy" legend.
Watering regularity in a vegetable garden app card — Heatmap Cell Animation example 2

② Watering regularity in a vegetable garden app card

WhenA vegetable garden app: the "Watering" card shows the litres poured each day over four weeks, next to the current streak and tonight's reminder. Cells fill in a wave when the card appears.
WhyGaps in the grid (dry days) stand out; gardeners see their habits without opening a history.
Settingsdata-unit="L", --hmp-color:34,160,90 (green), --hmp-stagger:35ms for a slower wave, "Dry / Soaked" legend, transparent background inside a white card.
A training log that recolours when you switch sport — Heatmap Cell Animation example 3

③ A training log that recolours when you switch sport

WhenA sports log: three filters, Running, Cycling and Swimming, above the month grid, with the total hours alongside. A click on a filter (or elsewhere in the scene) replaces data-values and the colour: the new pattern spreads cell by cell.
WhyThe wave makes the filter change readable: sessions appear where they happened instead of the grid jumping from one state to another.
Settingsdata-unit="min", --hmp-color tied to the chosen sport's colour (orange, blue, teal), --hmp-gap:5px, "Rest / Long" legend.

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 its data-value attribute.
  • Reduced motion: under prefers-reduced-motion: reduce the 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-value and 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.

Chrome 88+✓ Full (measured)
Firefox 89+✓ Full (measured)
Safari 15+✓ WebKit engine measured, not checked in real Safari
Edge 88+✓ Full (Chromium engine)
Safari iOS 15+✓ Not checked on a real device
Chrome Android 88+✓ Full (Chromium engine)

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):

index.html — structure
<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 / propertyDefaultEffect
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

Replace the series: 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.
The grid always starts on the first day of the week (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.
Each cell carries its value in 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.