Activity Grid
A grid of intensity-shaded cells showing activity or frequency over time (a contribution graph).
@garn/ui/activity-gridOn 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.
npx garn-ui add activity-grid- 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
- Give it an
aria-labelsummary; set per-celllabelfor the hover title. - Tune
levelsandmaxto the data's range.
- 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
- 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`.