Skip to main content

Catalog · Charts

Heat map

Shows how much in each cell of a grid by colour and by figure, from a native table; a calendar is one.

Fits best: app — a page that shows where a grid is high or low: a week by hours, a month of days

Example

Parcels in each part of the day, for the five working days.

Parcels per part of the day, this week. The stronger the colour, the more parcels.
MonTueWedThuFri
Morning120240360480600
Afternoon060300180540
Evening3090150270420

Known issues

None known.

Code

Copy the markup

Bundler
import "@luz-ds/catalog/components/heat-map.css";
HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/catalog@0.3.0/dist/components/heat-map.min.css" integrity="sha384-DJgfU9rFnD5GNimPOCLjdOuNzoYZICrPETuLVH/pOo2F3LcYvJLCAPq85abuQpaw" crossorigin="anonymous">

A stylesheet of @luz-ds/catalog: link components/heat-map.css after luz.css.

HTML
<table class="luz-heat-map">
  <caption>Parcels per part of the day, this week. The stronger the colour, the more parcels.</caption>
  <thead>
    <tr><td></td><th scope="col">Mon</th><th scope="col">Tue</th><th scope="col">Wed</th></tr>
  </thead>
  <tbody>
    <tr><th scope="row">Morning</th><td style="--v: .2">120</td><td style="--v: .4">240</td><td style="--v: .6">360</td></tr>
    <tr><th scope="row">Afternoon</th><td style="--v: 0">0</td><td style="--v: .1">60</td><td style="--v: .5">300</td></tr>
  </tbody>
</table>

Variants and options

OptionEffect
--v
On each cell, from 0 to 1: its value over the strongest value of the whole table
--luz-heat-map-color
Another chart colour for a table or a cell, such as var(--luz-chart-2)

How it works

A table with a head row of names and a name at the start of each row. Each cell is its figure in text, with a swatch above it that mixes --luz-chart-1 into --luz-chart-track in proportion to --v: the track at 0, the series at 1. The figure stays on the surface in the text colour, so it reads whatever the value.

Work out --v from your data: the value divided by the strongest value of the table, the same for every cell. A calendar is a heat map with the days of the week as columns and the weeks as rows. The inline style sets only --v; with a strict style-src, set it in a rule of your own.

The corner cell of the head row is an empty td. Keep to a handful of columns: with more than six, put the table in a luz-table-wrap.

Without JavaScript

It needs none. Without the stylesheet it reads as a table of figures.

Keyboard and ARIA

Nothing takes focus. A table keeps its roles, so a screen reader reads each figure with its row and column names. The caption names the table and says which way is more.

The swatch carries no text and no name: the figure is the value.

Contrast and focus are in the Design tab.

Design

When to use

Use it to see where the high and low values of a grid are: a week by hours, a month of days, a team by quarter.

When not to use

Do not use it for a few values (use a bar list or a column chart) or when the exact values matter more than the pattern (use a table).

Do and don't

Do Work every --v from the strongest value of the whole table, so one colour always means the same figure.
Don't Give each row a scale of its own: the night row, with 30 at most, looks as strong as the morning row with 600, and the colour no longer compares.

Writing the content

Say in the caption what the grid counts and which way is more. The colour is lighter for more in dark mode and darker in light mode, so write “the stronger the colour” and not “the darker”. The data and the labels are yours: the component only draws them.

Contrast and focus

The swatch is a chart colour on the surface; the empty one is the track, quiet on purpose, because the figure carries the value.

PartColour on backgroundLightDarkHigh contrastNeeds
Strongest swatch on the page
--luz-chart-1 on --luz-surface-page
5.6:1
7.7:1
14.1:1
3:1
Strongest swatch against the empty one
--luz-chart-1 on --luz-chart-track
3.4:1
3.8:1
6.8:1
2:1
Empty swatch on the page
--luz-chart-track on --luz-surface-page
1.6:1
2.0:1
2.0:1
1.5:1
Figure
--luz-text-primary on --luz-surface-page
15.8:1
15.1:1
21.0:1
4.5:1
Name
--luz-text-secondary on --luz-surface-page
6.2:1
7.5:1
19.3:1
4.5:1

Measured from the token values of each mode, rounded down. Needs is the WCAG 2.2 AA minimum: 4.5:1 for text, 3:1 for focus rings, borders and icons; a quiet decorative part keeps 2:1 against what it is drawn on.

  • Focus: nothing takes it.
  • Not colour alone: every swatch has its figure under it in text, and in forced colors the swatch is the system highlight mixed into the canvas, with an edge.

Tokens it uses

The 12 tokens that components/heat-map.css reads, by family. Each family links to its page, with the values in every mode. In Figma they are variables with the same names: see Tokens in Figma.

Spacing and sizing
--luz-space-2xl, --luz-space-2xs, --luz-space-3xs, --luz-space-lg
Typography
--luz-font-mono, --luz-font-sans, --luz-text-sm, --luz-text-xs
Radius
--luz-radius-sm

Keyboard and ARIA are in the Code tab.