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
Deploys, last 8 weeks
Datedeploys
Sep 21, 20264
Sep 22, 20266
Sep 23, 20263
Sep 24, 20267
Sep 25, 20262
Sep 26, 20260
Sep 27, 20260
Sep 28, 20265
Sep 29, 20268
Sep 30, 20266
Oct 1, 20264
Oct 2, 20263
Oct 3, 20261
Oct 4, 20260
Oct 5, 20266
Oct 6, 20269
Oct 7, 20267
Oct 8, 20265
Oct 9, 20262
Oct 10, 20260
Oct 11, 20260
Oct 12, 20263
Oct 13, 20264
Oct 14, 20268
Oct 15, 20266
Oct 16, 20261
Oct 17, 20260
Oct 18, 20261
Oct 19, 20267
Oct 20, 202610
Oct 21, 20266
Oct 22, 20269
Oct 23, 20264
Oct 24, 20260
Oct 25, 20260
Oct 26, 20265
Oct 27, 20267
Oct 28, 202611
Oct 29, 20268
Oct 30, 20263
Oct 31, 20261
Nov 1, 20260
Nov 2, 20266
Nov 3, 20268
Nov 4, 20265
Nov 5, 202612
Nov 6, 20264
Nov 7, 20260
Nov 8, 20260
Nov 9, 20267
Nov 10, 20269
Nov 11, 20266
Nov 12, 20268
Nov 13, 20263
Nov 14, 20260
Nov 15, 20260
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.

Commits to main, July to September 2026
Datecommits
Jul 1, 20263
Jul 2, 20267
Jul 3, 202612
Jul 4, 20260
Jul 5, 20260
Jul 6, 20262
Jul 7, 202614
Jul 8, 20266
Jul 9, 20263
Jul 10, 20267
Jul 11, 20260
Jul 12, 20260
Jul 13, 20269
Jul 14, 20262
Jul 15, 202614
Jul 16, 20260
Jul 17, 20263
Jul 18, 20260
Jul 19, 20260
Jul 20, 20265
Jul 21, 20269
Jul 22, 20262
Jul 23, 202614
Jul 24, 20266
Jul 25, 20260
Jul 26, 20260
Jul 27, 20260
Jul 28, 20265
Jul 29, 20269
Jul 30, 20262
Jul 31, 202614
Aug 1, 20260
Aug 2, 20260
Aug 3, 20267
Aug 4, 202612
Aug 5, 20265
Aug 6, 20269
Aug 7, 20260
Aug 8, 20260
Aug 9, 20260
Aug 10, 20263
Aug 11, 20267
Aug 12, 202612
Aug 13, 20265
Aug 14, 20269
Aug 15, 20260
Aug 16, 20260
Aug 17, 20266
Aug 18, 20260
Aug 19, 20267
Aug 20, 202612
Aug 21, 20265
Aug 22, 20260
Aug 23, 20260
Aug 24, 202614
Aug 25, 20266
Aug 26, 20263
Aug 27, 20267
Aug 28, 202612
Aug 29, 20260
Aug 30, 20260
Aug 31, 20262
Sep 1, 202614
Sep 2, 20266
Sep 3, 20263
Sep 4, 20267
Sep 5, 20260
Sep 6, 20260
Sep 7, 20269
Sep 8, 20262
Sep 9, 20260
Sep 10, 20266
Sep 11, 20263
Sep 12, 20260
Sep 13, 20260
Sep 14, 20265
Sep 15, 20269
Sep 16, 20262
Sep 17, 202614
Sep 18, 20266
Sep 19, 20260
Sep 20, 20260
Sep 21, 202612
Sep 22, 20265
Sep 23, 20269
Sep 24, 20262
Sep 25, 202614
Sep 26, 20260
Sep 27, 20260
Sep 28, 20267
Sep 29, 202612
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.

Incidents, September and October 2026
Dateincidents
Sep 1, 20260
Sep 2, 20260
Sep 3, 20260
Sep 4, 20261
Sep 5, 20260
Sep 6, 20260
Sep 7, 20260
Sep 8, 20260
Sep 9, 20260
Sep 10, 20260
Sep 11, 20260
Sep 12, 20260
Sep 13, 20260
Sep 14, 20260
Sep 15, 20260
Sep 16, 20260
Sep 17, 20263
Sep 18, 20261
Sep 19, 20260
Sep 20, 20260
Sep 21, 20260
Sep 22, 20260
Sep 23, 20260
Sep 24, 20260
Sep 25, 20260
Sep 26, 20260
Sep 27, 20260
Sep 28, 20260
Sep 29, 20260
Sep 30, 20260
Oct 1, 20260
Oct 2, 20262
Oct 3, 20260
Oct 4, 20260
Oct 5, 20260
Oct 6, 20260
Oct 7, 20260
Oct 8, 20260
Oct 9, 20260
Oct 10, 20260
Oct 11, 20260
Oct 12, 20260
Oct 13, 20260
Oct 14, 20260
Oct 15, 20260
Oct 16, 20260
Oct 17, 20260
Oct 18, 20260
Oct 19, 20260
Oct 20, 20260
Oct 21, 20261
Oct 22, 20260
Oct 23, 20260
Oct 24, 20260
Oct 25, 20260
Oct 26, 20260
Oct 27, 20260
Oct 28, 20260
Oct 29, 20260
Oct 30, 20260
Oct 31, 20260
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

PropTypeDefaultWhat it does
data{ date: string; value: number }[]noneISO dates with their value. Same-day entries are added; missing days count as zero.
labelstringnoneNames the chart. The total and busiest day are added to it.
fromstringnoneFirst day shown, as an ISO date. Required so the render never reads the clock.
tostringnoneLast day shown, as an ISO date.
levelsnumber4Steps of ink above empty.
weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 610 is Sunday, 1 is Monday.
unit{ one: string; other: string }noneWhat a value counts, so a cell reads "12 deploys on Nov 3, 2026".
localestring"en-US"Formats numbers, dates, months and weekdays.
...restComponentProps<"div">noneEvery native div attribute.

Anatomy

root.eq-heatmap
body.eq-heatmap-bodyMonth labels, weekday labels and the grid.
months.eq-heatmap-months
month.eq-heatmap-monthPlaced over the week that holds the first of the month.
days.eq-heatmap-days
day.eq-heatmap-dayMonday, Wednesday and Friday are named.
grid.eq-heatmap-gridrole="img" with the total and busiest day.
cell.eq-heatmap-celldata-level from 0; data-blank pads the first and last weeks.
legend.eq-heatmap-legend
data.eq-heatmap-dataA 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;
  }
}

EqUIp is in beta: free for non-commercial use, no warranty, and things can change. Terms

Nothing picked yet. Press A on a part, or use the plus on the board, and the install command builds here.

Loading the index.

Loading the index