Skip to content
garn

Activity Grid

A grid of intensity-shaded cells showing activity or frequency over time (a contribution graph).

Stablev0.4.2added in v0.2.0@garn/ui/activity-grid
On this page

Default

13 weeks of activity in stepped intensities.

Installation

garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.

terminal
npx garn-ui add activity-grid
First time? Set up garn in your project
Registry
activity-grid
Deps
None
Registry deps
utils

Import

import { ActivityGrid } from "@garn/ui/activity-grid";

Props

The component's public props and their types.

color
Description
Cell tint (token reference); empty cells use `bg-muted`.
Type
string
Default
"var(--garn-chart-4)"
itemsRequired
Description
The cells, in column-major order — each fills the next row, then the next column.
Type
ActivityCell[]
levels
Description
Intensity buckets including the empty level (5 → empty + 4 shades).
Type
number
Default
5
max
Description
Value mapped to the highest intensity. Defaults to the largest value in `items`.
Type
number
rows
Description
Grid rows; cells fill column-major (7 = days of a week).
Type
number
Default
7

Plus 278 inherited native <div> attributes.

Styling

Target these data-slots and states, and remap these tokens, to restyle without forking the component.

[data-slot="activity-grid"][data-slot="activity-cell"]

When to use

Show activity/frequency density over time as a calendar-style grid (contributions, logins, streaks).

Reach for something else when

  • A labelled value matrix with row/column axes (use Heatmap)
  • a numeric trend line (use Sparkline).

Overview

Activity Grid is the contribution-graph widget — a compact grid where each cell's shade encodes how much happened that day (commits, logins, events). It bins values into a few intensity levels and lays cells out column-major (weeks as columns), giving the familiar at-a-glance density map. Pure CSS grid, so it renders on the server with no engine.

Guidelines

Bin, don't scale precisely. The grid shows relative intensity across a few levels, not exact counts — pair it with a total or a hover title for the real number.

One hue, stepped opacity. Cells tint a single --garn-chart-* token at increasing opacity; empty cells are muted. That keeps the density readable; don't map cells to unrelated hues.

Summarise for assistive tech. It's one role=img — give it an aria-label stating the span and takeaway; per-cell detail lives in the title.

Best practices

Do
  • Give it an aria-label summary; set per-cell label for the hover title.
  • Tune levels and max to the data's range.
Don't
  • Don't expect exact counts to be readable from shade alone.
  • Don't use unrelated colours per cell — intensity is one hue.

Troubleshooting

Everything looks the same shade.

Cause. max too low/high, or too few levels for the range.

Fix. Omit max to auto-scale to the peak; raise levels for more contrast.

Accessibility

Role
img
A1.1.1Non-text Content
  • role=img with an `aria-label` summary (e.g. "Activity — last 13 weeks").
  • Each cell carries a hover `title` and reflects its bucket via `data-level`.
  • Intensity is stepped opacity of a `--garn-chart-*` token; empty cells use `bg-muted`.