Charts
Heatmap
A calendar of days as cells, weeks as columns, showing how much happened each day over a date range.
- Built on
CSS gridcolor-mix()Intl.DateTimeFormatIntl.PluralRules- Client JavaScript
- None
- Works without React
- Yes, as HTML and CSS
- Source, gzipped
- 2.9 kB
- Dependencies
- React
| Date | deploys |
|---|---|
| Sep 21, 2026 | 4 |
| Sep 22, 2026 | 6 |
| Sep 23, 2026 | 3 |
| Sep 24, 2026 | 7 |
| Sep 25, 2026 | 2 |
| Sep 26, 2026 | 0 |
| Sep 27, 2026 | 0 |
| Sep 28, 2026 | 5 |
| Sep 29, 2026 | 8 |
| Sep 30, 2026 | 6 |
| Oct 1, 2026 | 4 |
| Oct 2, 2026 | 3 |
| Oct 3, 2026 | 1 |
| Oct 4, 2026 | 0 |
| Oct 5, 2026 | 6 |
| Oct 6, 2026 | 9 |
| Oct 7, 2026 | 7 |
| Oct 8, 2026 | 5 |
| Oct 9, 2026 | 2 |
| Oct 10, 2026 | 0 |
| Oct 11, 2026 | 0 |
| Oct 12, 2026 | 3 |
| Oct 13, 2026 | 4 |
| Oct 14, 2026 | 8 |
| Oct 15, 2026 | 6 |
| Oct 16, 2026 | 1 |
| Oct 17, 2026 | 0 |
| Oct 18, 2026 | 1 |
| Oct 19, 2026 | 7 |
| Oct 20, 2026 | 10 |
| Oct 21, 2026 | 6 |
| Oct 22, 2026 | 9 |
| Oct 23, 2026 | 4 |
| Oct 24, 2026 | 0 |
| Oct 25, 2026 | 0 |
| Oct 26, 2026 | 5 |
| Oct 27, 2026 | 7 |
| Oct 28, 2026 | 11 |
| Oct 29, 2026 | 8 |
| Oct 30, 2026 | 3 |
| Oct 31, 2026 | 1 |
| Nov 1, 2026 | 0 |
| Nov 2, 2026 | 6 |
| Nov 3, 2026 | 8 |
| Nov 4, 2026 | 5 |
| Nov 5, 2026 | 12 |
| Nov 6, 2026 | 4 |
| Nov 7, 2026 | 0 |
| Nov 8, 2026 | 0 |
| Nov 9, 2026 | 7 |
| Nov 10, 2026 | 9 |
| Nov 11, 2026 | 6 |
| Nov 12, 2026 | 8 |
| Nov 13, 2026 | 3 |
| Nov 14, 2026 | 0 |
| Nov 15, 2026 | 0 |
Try its options
Loading its options.
Code
import { Heatmap } from "@/components/equip/heatmap";
/* Deploys per day from Monday 21 Sep 2026, weekends quiet. */
const perDay = [
4, 6, 3, 7, 2, 0, 0, 5, 8, 6, 4, 3, 1, 0, 6, 9, 7, 5, 2, 0, 0, 3, 4, 8, 6, 1, 0, 1,
7, 10, 6, 9, 4, 0, 0, 5, 7, 11, 8, 3, 1, 0, 6, 8, 5, 12, 4, 0, 0, 7, 9, 6, 8, 3, 0, 0,
];
const deploys = perDay.map((value, i) => ({
date: new Date(Date.UTC(2026, 8, 21 + i)).toISOString().slice(0, 10),
value,
}));
export default function Demo() {
return (
<Heatmap
data={deploys}
label="Deploys, last 8 weeks"
from="2026-09-21"
to="2026-11-15"
unit={{ one: "deploy", other: "deploys" }}
/>
);
}Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<div class="eq-heatmap" style="--_weeks:8">
<div class="eq-heatmap-body">
<div class="eq-heatmap-months" aria-hidden="true">
<span class="eq-heatmap-month" style="grid-column:2 / span 3">Oct</span>
<span class="eq-heatmap-month" style="grid-column:6 / span 3">Nov</span>
</div>
<div class="eq-heatmap-days" aria-hidden="true">
<span class="eq-heatmap-day">Mon</span>
<span class="eq-heatmap-day"></span>
<span class="eq-heatmap-day">Wed</span>
<span class="eq-heatmap-day"></span>
<span class="eq-heatmap-day">Fri</span>
<span class="eq-heatmap-day"></span>
<span class="eq-heatmap-day"></span>
</div>
<div class="eq-heatmap-grid" role="img" aria-label="Deploys, last 8 weeks: 240 deploys over 56 days, busiest Nov 5, 2026 with 12 deploys">
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="4 deploys on Sep 21, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="6 deploys on Sep 22, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="3 deploys on Sep 23, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="7 deploys on Sep 24, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="2 deploys on Sep 25, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Sep 26, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Sep 27, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="5 deploys on Sep 28, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="8 deploys on Sep 29, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="6 deploys on Sep 30, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="4 deploys on Oct 1, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="3 deploys on Oct 2, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="1 deploy on Oct 3, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Oct 4, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="6 deploys on Oct 5, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="9 deploys on Oct 6, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="7 deploys on Oct 7, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="5 deploys on Oct 8, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="2 deploys on Oct 9, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Oct 10, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Oct 11, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="3 deploys on Oct 12, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="4 deploys on Oct 13, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="8 deploys on Oct 14, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="6 deploys on Oct 15, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="1 deploy on Oct 16, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Oct 17, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="1 deploy on Oct 18, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="7 deploys on Oct 19, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:100%" title="10 deploys on Oct 20, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="6 deploys on Oct 21, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="9 deploys on Oct 22, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="4 deploys on Oct 23, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Oct 24, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Oct 25, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="5 deploys on Oct 26, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="7 deploys on Oct 27, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:100%" title="11 deploys on Oct 28, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="8 deploys on Oct 29, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="3 deploys on Oct 30, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="1 deploy on Oct 31, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Nov 1, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="6 deploys on Nov 2, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="8 deploys on Nov 3, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="5 deploys on Nov 4, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:100%" title="12 deploys on Nov 5, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="4 deploys on Nov 6, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Nov 7, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Nov 8, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="7 deploys on Nov 9, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="9 deploys on Nov 10, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%" title="6 deploys on Nov 11, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%" title="8 deploys on Nov 12, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="3 deploys on Nov 13, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Nov 14, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 deploys on Nov 15, 2026"></span>
</div>
</div>
<div class="eq-heatmap-legend" aria-hidden="true">
<span>Less</span>
<span class="eq-heatmap-cell" data-level="0"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:50%"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:75%"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:100%"></span>
<span>More</span>
</div>
<table class="eq-heatmap-data">
<caption>Deploys, last 8 weeks</caption>
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">deploys</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Sep 21, 2026</th>
<td>4</td>
</tr>
<tr>
<th scope="row">Sep 22, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Sep 23, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Sep 24, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Sep 25, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Sep 26, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 27, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 28, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Sep 29, 2026</th>
<td>8</td>
</tr>
<tr>
<th scope="row">Sep 30, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Oct 1, 2026</th>
<td>4</td>
</tr>
<tr>
<th scope="row">Oct 2, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Oct 3, 2026</th>
<td>1</td>
</tr>
<tr>
<th scope="row">Oct 4, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 5, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Oct 6, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Oct 7, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Oct 8, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Oct 9, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Oct 10, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 11, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 12, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Oct 13, 2026</th>
<td>4</td>
</tr>
<tr>
<th scope="row">Oct 14, 2026</th>
<td>8</td>
</tr>
<tr>
<th scope="row">Oct 15, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Oct 16, 2026</th>
<td>1</td>
</tr>
<tr>
<th scope="row">Oct 17, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 18, 2026</th>
<td>1</td>
</tr>
<tr>
<th scope="row">Oct 19, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Oct 20, 2026</th>
<td>10</td>
</tr>
<tr>
<th scope="row">Oct 21, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Oct 22, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Oct 23, 2026</th>
<td>4</td>
</tr>
<tr>
<th scope="row">Oct 24, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 25, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 26, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Oct 27, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Oct 28, 2026</th>
<td>11</td>
</tr>
<tr>
<th scope="row">Oct 29, 2026</th>
<td>8</td>
</tr>
<tr>
<th scope="row">Oct 30, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Oct 31, 2026</th>
<td>1</td>
</tr>
<tr>
<th scope="row">Nov 1, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Nov 2, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Nov 3, 2026</th>
<td>8</td>
</tr>
<tr>
<th scope="row">Nov 4, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Nov 5, 2026</th>
<td>12</td>
</tr>
<tr>
<th scope="row">Nov 6, 2026</th>
<td>4</td>
</tr>
<tr>
<th scope="row">Nov 7, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Nov 8, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Nov 9, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Nov 10, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Nov 11, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Nov 12, 2026</th>
<td>8</td>
</tr>
<tr>
<th scope="row">Nov 13, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Nov 14, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Nov 15, 2026</th>
<td>0</td>
</tr>
</tbody>
</table>
</div>Examples
A quarter, Sunday first
Levels split the busiest day into equal steps; a day with any activity is never drawn as empty.
| Date | commits |
|---|---|
| Jul 1, 2026 | 3 |
| Jul 2, 2026 | 7 |
| Jul 3, 2026 | 12 |
| Jul 4, 2026 | 0 |
| Jul 5, 2026 | 0 |
| Jul 6, 2026 | 2 |
| Jul 7, 2026 | 14 |
| Jul 8, 2026 | 6 |
| Jul 9, 2026 | 3 |
| Jul 10, 2026 | 7 |
| Jul 11, 2026 | 0 |
| Jul 12, 2026 | 0 |
| Jul 13, 2026 | 9 |
| Jul 14, 2026 | 2 |
| Jul 15, 2026 | 14 |
| Jul 16, 2026 | 0 |
| Jul 17, 2026 | 3 |
| Jul 18, 2026 | 0 |
| Jul 19, 2026 | 0 |
| Jul 20, 2026 | 5 |
| Jul 21, 2026 | 9 |
| Jul 22, 2026 | 2 |
| Jul 23, 2026 | 14 |
| Jul 24, 2026 | 6 |
| Jul 25, 2026 | 0 |
| Jul 26, 2026 | 0 |
| Jul 27, 2026 | 0 |
| Jul 28, 2026 | 5 |
| Jul 29, 2026 | 9 |
| Jul 30, 2026 | 2 |
| Jul 31, 2026 | 14 |
| Aug 1, 2026 | 0 |
| Aug 2, 2026 | 0 |
| Aug 3, 2026 | 7 |
| Aug 4, 2026 | 12 |
| Aug 5, 2026 | 5 |
| Aug 6, 2026 | 9 |
| Aug 7, 2026 | 0 |
| Aug 8, 2026 | 0 |
| Aug 9, 2026 | 0 |
| Aug 10, 2026 | 3 |
| Aug 11, 2026 | 7 |
| Aug 12, 2026 | 12 |
| Aug 13, 2026 | 5 |
| Aug 14, 2026 | 9 |
| Aug 15, 2026 | 0 |
| Aug 16, 2026 | 0 |
| Aug 17, 2026 | 6 |
| Aug 18, 2026 | 0 |
| Aug 19, 2026 | 7 |
| Aug 20, 2026 | 12 |
| Aug 21, 2026 | 5 |
| Aug 22, 2026 | 0 |
| Aug 23, 2026 | 0 |
| Aug 24, 2026 | 14 |
| Aug 25, 2026 | 6 |
| Aug 26, 2026 | 3 |
| Aug 27, 2026 | 7 |
| Aug 28, 2026 | 12 |
| Aug 29, 2026 | 0 |
| Aug 30, 2026 | 0 |
| Aug 31, 2026 | 2 |
| Sep 1, 2026 | 14 |
| Sep 2, 2026 | 6 |
| Sep 3, 2026 | 3 |
| Sep 4, 2026 | 7 |
| Sep 5, 2026 | 0 |
| Sep 6, 2026 | 0 |
| Sep 7, 2026 | 9 |
| Sep 8, 2026 | 2 |
| Sep 9, 2026 | 0 |
| Sep 10, 2026 | 6 |
| Sep 11, 2026 | 3 |
| Sep 12, 2026 | 0 |
| Sep 13, 2026 | 0 |
| Sep 14, 2026 | 5 |
| Sep 15, 2026 | 9 |
| Sep 16, 2026 | 2 |
| Sep 17, 2026 | 14 |
| Sep 18, 2026 | 6 |
| Sep 19, 2026 | 0 |
| Sep 20, 2026 | 0 |
| Sep 21, 2026 | 12 |
| Sep 22, 2026 | 5 |
| Sep 23, 2026 | 9 |
| Sep 24, 2026 | 2 |
| Sep 25, 2026 | 14 |
| Sep 26, 2026 | 0 |
| Sep 27, 2026 | 0 |
| Sep 28, 2026 | 7 |
| Sep 29, 2026 | 12 |
Code
import { Heatmap } from "@/components/equip/heatmap";
/* A fixed pattern, so the render is the same on every build. */
const commits = Array.from({ length: 91 }, (_, i) => {
const weekday = (i + 3) % 7;
const base = weekday === 0 || weekday === 6 ? 0 : [3, 7, 12, 5, 9, 2, 14, 6][i % 8] ?? 0;
return {
date: new Date(Date.UTC(2026, 6, 1 + i)).toISOString().slice(0, 10),
value: i % 11 === 4 ? 0 : base,
};
});
export default function Example() {
return (
<Heatmap
data={commits}
label="Commits to main, July to September 2026"
from="2026-07-01"
to="2026-09-29"
weekStartsOn={0}
levels={5}
unit={{ one: "commit", other: "commits" }}
/>
);
}HTMLworks in any framework, with the part's CSS
<div class="eq-heatmap" style="--_weeks:14">
<div class="eq-heatmap-body">
<div class="eq-heatmap-months" aria-hidden="true">
<span class="eq-heatmap-month" style="grid-column:1 / span 3">Jul</span>
<span class="eq-heatmap-month" style="grid-column:5 / span 3">Aug</span>
<span class="eq-heatmap-month" style="grid-column:10 / span 3">Sep</span>
</div>
<div class="eq-heatmap-days" aria-hidden="true">
<span class="eq-heatmap-day"></span>
<span class="eq-heatmap-day">Mon</span>
<span class="eq-heatmap-day"></span>
<span class="eq-heatmap-day">Wed</span>
<span class="eq-heatmap-day"></span>
<span class="eq-heatmap-day">Fri</span>
<span class="eq-heatmap-day"></span>
</div>
<div class="eq-heatmap-grid" role="img" aria-label="Commits to main, July to September 2026: 438 commits over 91 days, busiest Jul 7, 2026 with 14 commits">
<span class="eq-heatmap-cell" data-blank=""></span>
<span class="eq-heatmap-cell" data-blank=""></span>
<span class="eq-heatmap-cell" data-blank=""></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="3 commits on Jul 1, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="7 commits on Jul 2, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="12 commits on Jul 3, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Jul 4, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Jul 5, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="2 commits on Jul 6, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="14 commits on Jul 7, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="6 commits on Jul 8, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="3 commits on Jul 9, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="7 commits on Jul 10, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Jul 11, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Jul 12, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:81%" title="9 commits on Jul 13, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="2 commits on Jul 14, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="14 commits on Jul 15, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Jul 16, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="3 commits on Jul 17, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Jul 18, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Jul 19, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="5 commits on Jul 20, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:81%" title="9 commits on Jul 21, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="2 commits on Jul 22, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="14 commits on Jul 23, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="6 commits on Jul 24, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Jul 25, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Jul 26, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Jul 27, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="5 commits on Jul 28, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:81%" title="9 commits on Jul 29, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="2 commits on Jul 30, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="14 commits on Jul 31, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 1, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 2, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="7 commits on Aug 3, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="12 commits on Aug 4, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="5 commits on Aug 5, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:81%" title="9 commits on Aug 6, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 7, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 8, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 9, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="3 commits on Aug 10, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="7 commits on Aug 11, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="12 commits on Aug 12, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="5 commits on Aug 13, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:81%" title="9 commits on Aug 14, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 15, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 16, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="6 commits on Aug 17, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 18, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="7 commits on Aug 19, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="12 commits on Aug 20, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="5 commits on Aug 21, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 22, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 23, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="14 commits on Aug 24, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="6 commits on Aug 25, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="3 commits on Aug 26, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="7 commits on Aug 27, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="12 commits on Aug 28, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 29, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Aug 30, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="2 commits on Aug 31, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="14 commits on Sep 1, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="6 commits on Sep 2, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="3 commits on Sep 3, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="7 commits on Sep 4, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Sep 5, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Sep 6, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:81%" title="9 commits on Sep 7, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="2 commits on Sep 8, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Sep 9, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="6 commits on Sep 10, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="3 commits on Sep 11, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Sep 12, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Sep 13, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="5 commits on Sep 14, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:81%" title="9 commits on Sep 15, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="2 commits on Sep 16, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="14 commits on Sep 17, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="6 commits on Sep 18, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Sep 19, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Sep 20, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="12 commits on Sep 21, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%" title="5 commits on Sep 22, 2026"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:81%" title="9 commits on Sep 23, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="2 commits on Sep 24, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="14 commits on Sep 25, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Sep 26, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 commits on Sep 27, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%" title="7 commits on Sep 28, 2026"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%" title="12 commits on Sep 29, 2026"></span>
<span class="eq-heatmap-cell" data-blank=""></span>
<span class="eq-heatmap-cell" data-blank=""></span>
<span class="eq-heatmap-cell" data-blank=""></span>
<span class="eq-heatmap-cell" data-blank=""></span>
</div>
</div>
<div class="eq-heatmap-legend" aria-hidden="true">
<span>Less</span>
<span class="eq-heatmap-cell" data-level="0"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:44%"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:63%"></span>
<span class="eq-heatmap-cell" data-level="4" style="--_k:81%"></span>
<span class="eq-heatmap-cell" data-level="5" style="--_k:100%"></span>
<span>More</span>
</div>
<table class="eq-heatmap-data">
<caption>Commits to main, July to September 2026</caption>
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">commits</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Jul 1, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Jul 2, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Jul 3, 2026</th>
<td>12</td>
</tr>
<tr>
<th scope="row">Jul 4, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Jul 5, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Jul 6, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Jul 7, 2026</th>
<td>14</td>
</tr>
<tr>
<th scope="row">Jul 8, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Jul 9, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Jul 10, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Jul 11, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Jul 12, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Jul 13, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Jul 14, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Jul 15, 2026</th>
<td>14</td>
</tr>
<tr>
<th scope="row">Jul 16, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Jul 17, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Jul 18, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Jul 19, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Jul 20, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Jul 21, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Jul 22, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Jul 23, 2026</th>
<td>14</td>
</tr>
<tr>
<th scope="row">Jul 24, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Jul 25, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Jul 26, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Jul 27, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Jul 28, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Jul 29, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Jul 30, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Jul 31, 2026</th>
<td>14</td>
</tr>
<tr>
<th scope="row">Aug 1, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 2, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 3, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Aug 4, 2026</th>
<td>12</td>
</tr>
<tr>
<th scope="row">Aug 5, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Aug 6, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Aug 7, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 8, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 9, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 10, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Aug 11, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Aug 12, 2026</th>
<td>12</td>
</tr>
<tr>
<th scope="row">Aug 13, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Aug 14, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Aug 15, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 16, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 17, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Aug 18, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 19, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Aug 20, 2026</th>
<td>12</td>
</tr>
<tr>
<th scope="row">Aug 21, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Aug 22, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 23, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 24, 2026</th>
<td>14</td>
</tr>
<tr>
<th scope="row">Aug 25, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Aug 26, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Aug 27, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Aug 28, 2026</th>
<td>12</td>
</tr>
<tr>
<th scope="row">Aug 29, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 30, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Aug 31, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Sep 1, 2026</th>
<td>14</td>
</tr>
<tr>
<th scope="row">Sep 2, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Sep 3, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Sep 4, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Sep 5, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 6, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 7, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Sep 8, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Sep 9, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 10, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Sep 11, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Sep 12, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 13, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 14, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Sep 15, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Sep 16, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Sep 17, 2026</th>
<td>14</td>
</tr>
<tr>
<th scope="row">Sep 18, 2026</th>
<td>6</td>
</tr>
<tr>
<th scope="row">Sep 19, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 20, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 21, 2026</th>
<td>12</td>
</tr>
<tr>
<th scope="row">Sep 22, 2026</th>
<td>5</td>
</tr>
<tr>
<th scope="row">Sep 23, 2026</th>
<td>9</td>
</tr>
<tr>
<th scope="row">Sep 24, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Sep 25, 2026</th>
<td>14</td>
</tr>
<tr>
<th scope="row">Sep 26, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 27, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 28, 2026</th>
<td>7</td>
</tr>
<tr>
<th scope="row">Sep 29, 2026</th>
<td>12</td>
</tr>
</tbody>
</table>
</div>Sparse data
Only days with activity need an entry; missing days count as zero and entries outside from and to are ignored.
| Date | incidents |
|---|---|
| Sep 1, 2026 | 0 |
| Sep 2, 2026 | 0 |
| Sep 3, 2026 | 0 |
| Sep 4, 2026 | 1 |
| Sep 5, 2026 | 0 |
| Sep 6, 2026 | 0 |
| Sep 7, 2026 | 0 |
| Sep 8, 2026 | 0 |
| Sep 9, 2026 | 0 |
| Sep 10, 2026 | 0 |
| Sep 11, 2026 | 0 |
| Sep 12, 2026 | 0 |
| Sep 13, 2026 | 0 |
| Sep 14, 2026 | 0 |
| Sep 15, 2026 | 0 |
| Sep 16, 2026 | 0 |
| Sep 17, 2026 | 3 |
| Sep 18, 2026 | 1 |
| Sep 19, 2026 | 0 |
| Sep 20, 2026 | 0 |
| Sep 21, 2026 | 0 |
| Sep 22, 2026 | 0 |
| Sep 23, 2026 | 0 |
| Sep 24, 2026 | 0 |
| Sep 25, 2026 | 0 |
| Sep 26, 2026 | 0 |
| Sep 27, 2026 | 0 |
| Sep 28, 2026 | 0 |
| Sep 29, 2026 | 0 |
| Sep 30, 2026 | 0 |
| Oct 1, 2026 | 0 |
| Oct 2, 2026 | 2 |
| Oct 3, 2026 | 0 |
| Oct 4, 2026 | 0 |
| Oct 5, 2026 | 0 |
| Oct 6, 2026 | 0 |
| Oct 7, 2026 | 0 |
| Oct 8, 2026 | 0 |
| Oct 9, 2026 | 0 |
| Oct 10, 2026 | 0 |
| Oct 11, 2026 | 0 |
| Oct 12, 2026 | 0 |
| Oct 13, 2026 | 0 |
| Oct 14, 2026 | 0 |
| Oct 15, 2026 | 0 |
| Oct 16, 2026 | 0 |
| Oct 17, 2026 | 0 |
| Oct 18, 2026 | 0 |
| Oct 19, 2026 | 0 |
| Oct 20, 2026 | 0 |
| Oct 21, 2026 | 1 |
| Oct 22, 2026 | 0 |
| Oct 23, 2026 | 0 |
| Oct 24, 2026 | 0 |
| Oct 25, 2026 | 0 |
| Oct 26, 2026 | 0 |
| Oct 27, 2026 | 0 |
| Oct 28, 2026 | 0 |
| Oct 29, 2026 | 0 |
| Oct 30, 2026 | 0 |
| Oct 31, 2026 | 0 |
Code
import { Heatmap } from "@/components/equip/heatmap";
const incidents = [
{ date: "2026-08-30", value: 1 },
{ date: "2026-09-04", value: 1 },
{ date: "2026-09-17", value: 3 },
{ date: "2026-09-18", value: 1 },
{ date: "2026-10-02", value: 2 },
{ date: "2026-10-21", value: 1 },
];
export default function Example() {
return (
<Heatmap
data={incidents}
label="Incidents, September and October 2026"
from="2026-09-01"
to="2026-10-31"
levels={3}
unit={{ one: "incident", other: "incidents" }}
/>
);
}HTMLworks in any framework, with the part's CSS
<div class="eq-heatmap" style="--_weeks:9">
<div class="eq-heatmap-body">
<div class="eq-heatmap-months" aria-hidden="true">
<span class="eq-heatmap-month" style="grid-column:1 / span 3">Sep</span>
<span class="eq-heatmap-month" style="grid-column:5 / span 3">Oct</span>
</div>
<div class="eq-heatmap-days" aria-hidden="true">
<span class="eq-heatmap-day">Mon</span>
<span class="eq-heatmap-day"></span>
<span class="eq-heatmap-day">Wed</span>
<span class="eq-heatmap-day"></span>
<span class="eq-heatmap-day">Fri</span>
<span class="eq-heatmap-day"></span>
<span class="eq-heatmap-day"></span>
</div>
<div class="eq-heatmap-grid" role="img" aria-label="Incidents, September and October 2026: 8 incidents over 61 days, busiest Sep 17, 2026 with 3 incidents">
<span class="eq-heatmap-cell" data-blank=""></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 1, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 2, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 3, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="1 incident on Sep 4, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 5, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 6, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 7, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 8, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 9, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 10, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 11, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 12, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 13, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 14, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 15, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 16, 2026"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:100%" title="3 incidents on Sep 17, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="1 incident on Sep 18, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 19, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 20, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 21, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 22, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 23, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 24, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 25, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 26, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 27, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 28, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 29, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Sep 30, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 1, 2026"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:63%" title="2 incidents on Oct 2, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 3, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 4, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 5, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 6, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 7, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 8, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 9, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 10, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 11, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 12, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 13, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 14, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 15, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 16, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 17, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 18, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 19, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 20, 2026"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%" title="1 incident on Oct 21, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 22, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 23, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 24, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 25, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 26, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 27, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 28, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 29, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 30, 2026"></span>
<span class="eq-heatmap-cell" data-level="0" title="0 incidents on Oct 31, 2026"></span>
<span class="eq-heatmap-cell" data-blank=""></span>
</div>
</div>
<div class="eq-heatmap-legend" aria-hidden="true">
<span>Less</span>
<span class="eq-heatmap-cell" data-level="0"></span>
<span class="eq-heatmap-cell" data-level="1" style="--_k:25%"></span>
<span class="eq-heatmap-cell" data-level="2" style="--_k:63%"></span>
<span class="eq-heatmap-cell" data-level="3" style="--_k:100%"></span>
<span>More</span>
</div>
<table class="eq-heatmap-data">
<caption>Incidents, September and October 2026</caption>
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">incidents</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Sep 1, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 2, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 3, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 4, 2026</th>
<td>1</td>
</tr>
<tr>
<th scope="row">Sep 5, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 6, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 7, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 8, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 9, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 10, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 11, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 12, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 13, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 14, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 15, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 16, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 17, 2026</th>
<td>3</td>
</tr>
<tr>
<th scope="row">Sep 18, 2026</th>
<td>1</td>
</tr>
<tr>
<th scope="row">Sep 19, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 20, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 21, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 22, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 23, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 24, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 25, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 26, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 27, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 28, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 29, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Sep 30, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 1, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 2, 2026</th>
<td>2</td>
</tr>
<tr>
<th scope="row">Oct 3, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 4, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 5, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 6, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 7, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 8, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 9, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 10, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 11, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 12, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 13, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 14, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 15, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 16, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 17, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 18, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 19, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 20, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 21, 2026</th>
<td>1</td>
</tr>
<tr>
<th scope="row">Oct 22, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 23, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 24, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 25, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 26, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 27, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 28, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 29, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 30, 2026</th>
<td>0</td>
</tr>
<tr>
<th scope="row">Oct 31, 2026</th>
<td>0</td>
</tr>
</tbody>
</table>
</div>Props
| Prop | Type | Default | What it does |
|---|---|---|---|
data | { date: string; value: number }[] | none | ISO dates with their value. Same-day entries are added; missing days count as zero. |
label | string | none | Names the chart. The total and busiest day are added to it. |
from | string | none | First day shown, as an ISO date. Required so the render never reads the clock. |
to | string | none | Last day shown, as an ISO date. |
levels | number | 4 | Steps of ink above empty. |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 1 | 0 is Sunday, 1 is Monday. |
unit | { one: string; other: string } | none | What a value counts, so a cell reads "12 deploys on Nov 3, 2026". |
locale | string | "en-US" | Formats numbers, dates, months and weekdays. |
...rest | ComponentProps<"div"> | none | Every native div attribute. |
Anatomy
| root | .eq-heatmap | |
|---|---|---|
| body | .eq-heatmap-body | Month labels, weekday labels and the grid. |
| months | .eq-heatmap-months | |
| month | .eq-heatmap-month | Placed over the week that holds the first of the month. |
| days | .eq-heatmap-days | |
| day | .eq-heatmap-day | Monday, Wednesday and Friday are named. |
| grid | .eq-heatmap-grid | role="img" with the total and busiest day. |
| cell | .eq-heatmap-cell | data-level from 0; data-blank pads the first and last weeks. |
| legend | .eq-heatmap-legend | |
| data | .eq-heatmap-data | A visually hidden table of every day. |
Notes
- The grid is one image named with the total and busiest day, and a visually hidden table lists every day, so each value is reachable by assistive tech.
- Each cell has a title for pointer users; titles are not read inside the image, which is why the table exists.
- Dates are read and drawn in UTC, so the same data gives the same grid in every time zone.
- The Less and More legend words and the table headings are English; wrap or fork the part to translate them.
- Renders on the server with no client JavaScript.
Source
Two files, and they are the whole part. Open either to read it, copy it straight from its bar, or add the part to your loadout and install it with everything else you picked.
components/equip/heatmap.tsx159 lines
import type { CSSProperties, ComponentProps } from "react";
import "./heatmap.css";
export type HeatmapProps = ComponentProps<"div"> & {
/** One entry per day with activity, as ISO dates. Entries on the same day are added up. */
data: { date: string; value: number }[];
/** Names the chart, such as "Deploys, last 8 weeks". The total and busiest day are added to it. */
label: string;
/** First day shown, as an ISO date. Required so the render never depends on the clock. */
from: string;
/** Last day shown, as an ISO date. */
to: string;
/** How many steps of ink above empty. */
levels?: number;
/** 0 is Sunday, 1 is Monday. */
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
/** What a value counts, as singular and plural, such as { one: "deploy", other: "deploys" }. */
unit?: { one: string; other: string };
locale?: string;
};
const DAY = 86_400_000;
function parse(iso: string) {
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
return m ? Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])) : Number.NaN;
}
export function Heatmap({
data,
label,
from,
to,
levels = 4,
weekStartsOn = 1,
unit,
locale = "en-US",
className,
style,
...rest
}: HeatmapProps) {
const steps = Math.max(1, Math.floor(levels));
const start = parse(from);
const end = parse(to);
const number = new Intl.NumberFormat(locale);
const plural = new Intl.PluralRules(locale);
const longDate = new Intl.DateTimeFormat(locale, { day: "numeric", month: "short", year: "numeric", timeZone: "UTC" });
const monthName = new Intl.DateTimeFormat(locale, { month: "short", timeZone: "UTC" });
const dayName = new Intl.DateTimeFormat(locale, { weekday: "short", timeZone: "UTC" });
const count = (v: number) =>
unit ? `${number.format(v)} ${plural.select(v) === "one" ? unit.one : unit.other}` : number.format(v);
const byDay = new Map<number, number>();
for (const d of data) {
const t = parse(d.date);
if (Number.isFinite(t) && Number.isFinite(d.value)) byDay.set(t, (byDay.get(t) ?? 0) + d.value);
}
const days: { t: number; v: number }[] = [];
if (Number.isFinite(start) && Number.isFinite(end)) {
for (let t = start; t <= end; t += DAY) days.push({ t, v: byDay.get(t) ?? 0 });
}
const first = days[0];
const lead = first ? (new Date(first.t).getUTCDay() - weekStartsOn + 7) % 7 : 0;
const cells: ({ t: number; v: number } | null)[] = [...Array<null>(lead).fill(null), ...days];
while (cells.length % 7 !== 0) cells.push(null);
const weeks = cells.length / 7;
const peak = days.reduce<{ t: number; v: number } | undefined>((best, d) => (!best || d.v > best.v ? d : best), undefined);
const high = peak?.v ?? 0;
const total = days.reduce((sum, d) => sum + d.v, 0);
const level = (v: number) => (v <= 0 || high <= 0 ? 0 : Math.min(steps, Math.max(1, Math.ceil((v / high) * steps))));
const strength = (k: number) => ({ "--_k": `${steps === 1 ? 100 : Math.round(25 + ((k - 1) / (steps - 1)) * 75)}%` }) as CSSProperties;
const months: { col: number; name: string }[] = [];
cells.forEach((c, i) => {
if (c && new Date(c.t).getUTCDate() === 1) months.push({ col: Math.floor(i / 7), name: monthName.format(c.t) });
});
if (first && new Date(first.t).getUTCDate() !== 1 && (months[0]?.col ?? Infinity) > 2) {
months.unshift({ col: 0, name: monthName.format(first.t) });
}
const summary =
days.length === 0
? `${label}: no days in range`
: peak && high > 0
? `${label}: ${count(total)} over ${days.length} days, busiest ${longDate.format(peak.t)} with ${count(high)}`
: `${label}: nothing over ${days.length} days`;
return (
<div
{...rest}
className={["eq-heatmap", className].filter(Boolean).join(" ")}
style={{ ...style, "--_weeks": weeks } as CSSProperties}
>
<div className="eq-heatmap-body">
<div className="eq-heatmap-months" aria-hidden="true">
{months.map((m) => (
<span key={m.col} className="eq-heatmap-month" style={{ gridColumn: `${m.col + 1} / span 3` }}>
{m.name}
</span>
))}
</div>
<div className="eq-heatmap-days" aria-hidden="true">
{Array.from({ length: 7 }, (_, r) => {
const weekday = (weekStartsOn + r) % 7;
return (
<span key={r} className="eq-heatmap-day">
{weekday % 2 === 1 ? dayName.format(Date.UTC(1970, 0, 4 + weekday)) : null}
</span>
);
})}
</div>
<div className="eq-heatmap-grid" role="img" aria-label={summary}>
{cells.map((c, i) =>
c ? (
<span
key={i}
className="eq-heatmap-cell"
data-level={level(c.v)}
style={level(c.v) > 0 ? strength(level(c.v)) : undefined}
title={`${count(c.v)} on ${longDate.format(c.t)}`}
/>
) : (
<span key={i} className="eq-heatmap-cell" data-blank="" />
),
)}
</div>
</div>
<div className="eq-heatmap-legend" aria-hidden="true">
<span>Less</span>
{Array.from({ length: steps + 1 }, (_, k) => (
<span key={k} className="eq-heatmap-cell" data-level={k} style={k > 0 ? strength(k) : undefined} />
))}
<span>More</span>
</div>
{days.length > 0 && (
<table className="eq-heatmap-data">
<caption>{label}</caption>
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">{unit ? unit.other : "Value"}</th>
</tr>
</thead>
<tbody>
{days.map((d) => (
<tr key={d.t}>
<th scope="row">{longDate.format(d.t)}</th>
<td>{number.format(d.v)}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}components/equip/heatmap.css109 lines
@layer equip {
.eq-heatmap {
--_cell: 10px;
--_gap: 2px;
position: relative;
display: inline-grid;
gap: var(--eq-space-2);
font: var(--eq-weight) var(--eq-text-xs) / 1 var(--eq-font);
color: var(--eq-ink-2);
}
.eq-heatmap-body {
display: grid;
grid-template-columns: auto auto;
grid-template-areas:
". months"
"days grid";
gap: var(--eq-space-1);
}
.eq-heatmap-months {
grid-area: months;
display: grid;
grid-template-columns: repeat(var(--_weeks), var(--_cell));
column-gap: var(--_gap);
}
.eq-heatmap-month {
grid-row: 1;
white-space: nowrap;
}
.eq-heatmap-days {
grid-area: days;
display: grid;
grid-template-rows: repeat(7, var(--_cell));
row-gap: var(--_gap);
}
.eq-heatmap-day {
line-height: var(--_cell);
text-align: end;
}
.eq-heatmap-grid {
grid-area: grid;
display: grid;
grid-template-rows: repeat(7, var(--_cell));
grid-auto-flow: column;
grid-auto-columns: var(--_cell);
gap: var(--_gap);
}
.eq-heatmap-cell {
display: block;
width: var(--_cell);
height: var(--_cell);
border-radius: 2px;
background: var(--eq-tint);
transition: scale var(--eq-dur-move) var(--eq-spring);
}
.eq-heatmap-cell:not([data-level="0"], [data-blank]) {
background: color-mix(in srgb, var(--eq-ink) var(--_k), var(--eq-tint));
}
.eq-heatmap-cell[data-blank] {
background: transparent;
}
@media (hover: hover) {
.eq-heatmap-grid .eq-heatmap-cell:not([data-blank]):hover {
position: relative;
z-index: 1;
scale: 1.4;
box-shadow: 0 0 0 1px var(--eq-raised);
}
}
.eq-heatmap-legend {
display: flex;
align-items: center;
justify-self: end;
gap: var(--_gap);
}
.eq-heatmap-legend > span:first-child {
margin-inline-end: var(--eq-space-1);
}
.eq-heatmap-legend > span:last-child {
margin-inline-start: var(--eq-space-1);
}
.eq-heatmap-data {
/* A table ignores height and overflow, so it is made a block before it is clipped. */
display: block;
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
}