bigbullui logobigbullui
Componentcharts

Heatmap

Grid of colored cells showing value intensities with diagonal stagger

Preview

Live Preview
Mon: 5Mon: 3Mon: 8Mon: 2Tue: 7Tue: 2Tue: 5Tue: 6Wed: 3Wed: 9Wed: 1Wed: 4MonTueWed

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add heatmap

Or install the full npm package:

npm install bigbullui

Usage

import { Heatmap } from "@/components/ui/heatmap";

<Heatmap rows={rows} columns={7} />

Source code

heatmap.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface HeatmapRow {
  label: string;
  cells: { value: number }[];
}

export interface HeatmapProps extends React.HTMLAttributes<HTMLDivElement> {
  rows: HeatmapRow[];
  columns?: number;
  height?: number;
}

export function Heatmap({
  rows,
  columns = 7,
  height = 300,
  className,
  ...props
}: HeatmapProps) {
  if (rows.length === 0) {
    return <div className={cn("w-full", className)} {...props} />;
  }

  const maxValue = Math.max(
    ...rows.flatMap((row) => row.cells.map((c) => c.value)),
    0,
  );
  const CELL = 22;
  const GAP = 4;
  const STEP = CELL + GAP;
  const LABEL_W = 44;
  const PAD = 6;
  const cols = Math.max(...rows.map((r) => Math.min(r.cells.length, columns)), 1);
  const gridW = LABEL_W + cols * STEP + PAD;
  const gridH = rows.length * STEP + PAD;

  return (
    <div
      className={cn("w-full", "motion-reduce:animate-none", className)}
      style={{ height: `${height}px` }}
    >
      <svg
        className="h-full w-full"
        viewBox={`0 0 ${gridW} ${gridH}`}
        preserveAspectRatio="xMidYMid meet"
      >
        {rows.map((row, rowIdx) =>
          row.cells.slice(0, columns).map((cell, cellIdx) => {
            const value = cell.value;
            const intensity = Math.min(value / (maxValue || 1), 1);
            const bgOpacity = 0.1 + intensity * 0.8;
            const delay = rowIdx * 20 + cellIdx * 10;

            return (
              <React.Fragment key={`${row.label}-${cellIdx}`}>
              <rect
                x={PAD + LABEL_W + cellIdx * STEP}
                y={PAD + rowIdx * STEP}
                width={CELL}
                height={CELL}
                rx={4}
                fill="currentColor"
                opacity={bgOpacity > 0.9 ? 0.9 : bgOpacity}
                className={cn(
                  "motion-reduce:transition-none",
                  `animate-[fade-in-up_0.3s_ease-out_both_${delay}ms]`,
                )}
              >
                <title>{`${row.label}: ${value}`}</title>
              </rect>
              </React.Fragment>
            );
          }),
        )}

        {rows.map((row, rowIdx) => (
          <text
            key={`row-label-${rowIdx}`}
            x={PAD}
            y={PAD + rowIdx * STEP + 15}
            fontSize="9"
            className={cn(
              "motion-reduce:transition-none",
              "fill-muted-foreground font-mono uppercase",
            )}
          >
            {row.label}
          </text>
        ))}
      </svg>
    </div>
  );
}

Props

PropTypeDescription
rowsHeatmapRow[]Rows of cells with labels and values.
columnsnumberNumber of columns in the grid (default: 7).