A compact daily activity heatmap with token-based intensity and an accessible summary.
import { ContributionGraph } from "@/components/ballmac/contribution-graph"
const data = Array.from({ length: 84 }, (_, i) => ({
date: new Date(Date.UTC(2026, 6, i + 1)).toISOString().slice(0, 10),
count: (i * 7 + Math.floor(i / 5)) % 12,
}))
export default function ContributionGraphDemo() {
return (
<ContributionGraph
className="w-full max-w-lg"
label="Workspace activity"
data={data}
/>
)
}Installation
$ pnpm dlx shadcn@latest add @ballmac/contribution-graphAdd the Ballmac items it builds on.
$ pnpm dlx shadcn@latest add @ballmac/i18nCopy the source into your project.
components/ballmac/contribution-graph.tsx// Ballmac UI: Contribution Graph. https://ui.ballmac.com/components/contribution-graph "use client" import * as React from "react" import { cn } from "@/lib/utils" import { useLocale, useMessages } from "@/lib/ballmac/i18n" type ContributionDay = { /** ISO date (YYYY-MM-DD). */ date: string /** Contribution count. */ count: number } type ContributionGraphProps = React.ComponentProps<"div"> & { /** Daily values in calendar order. */ data: ContributionDay[] /** Accessible description of the measure. */ label: string /** Number of color levels for nonzero values. */ levels?: 2 | 3 | 4 } function ContributionGraph({ className, data, label, levels = 4, ...props }: ContributionGraphProps) { const msg = useMessages() const locale = useLocale() const total = data.reduce((sum, day) => sum + Math.max(0, day.count), 0) const max = Math.max(1, ...data.map((day) => day.count)) return ( <div data-slot="contribution-graph" role="img" tabIndex={0} aria-label={data.length ? msg("contribution-graph.summaryRange", { one: "{label}: {total} total across {count} day, {from} to {to}", other: "{label}: {total} total across {count} days, {from} to {to}" }, { label, total, count: data.length, from: data[0].date, to: data.at(-1)?.date ?? "" }) : msg("contribution-graph.summary", "{label}: {total} total across 0 days", { label, total })} className={cn( "min-w-0 max-w-full overflow-x-auto rounded-xl border border-border bg-card p-4", className, )} {...props} > <div aria-hidden="true" className="grid w-max grid-flow-col grid-rows-7 gap-1" > {data.map((day) => { const level = day.count <= 0 ? 0 : Math.max(1, Math.ceil((day.count / max) * levels)) return ( <span key={day.date} data-slot="contribution-day" data-level={level} title={`${day.date}: ${day.count}`} className={cn( "size-3 rounded-[3px] border border-border/50", level === 0 ? "bg-muted" : level === 1 ? "bg-primary/25" : level === 2 ? "bg-primary/45" : level === 3 ? "bg-primary/70" : "bg-primary", )} /> ) })} </div> <div className="text-muted-foreground mt-3 flex items-center justify-between gap-3 text-xs"> <span>{msg("contribution-graph.total", { one: "{total} contribution", other: "{total} contributions" }, { count: total, total: total.toLocaleString(locale) })}</span> <span className="whitespace-nowrap"> {msg("contribution-graph.less", "Less")}{" "} <span aria-hidden="true" className="bg-muted inline-block size-2 rounded-sm" />{" "} ·{" "} <span aria-hidden="true" className="bg-primary inline-block size-2 rounded-sm" />{" "} {msg("contribution-graph.more", "More")} </span> </div> </div> ) } export { ContributionGraph, type ContributionGraphProps, type ContributionDay }Update the import paths to match your project setup.
Usage
import { ContributionGraph } from "@/components/ballmac/contribution-graph"The full example is in the Code tab above.
Examples
States and variants
import { ContributionGraph } from "@/components/ballmac/contribution-graph"
const data = Array.from({ length: 35 }, (_, i) => ({
date: `day-${i}`,
count: i % 5 === 0 ? 0 : i % 4,
}))
export default function ContributionGraphStates() {
return (
<ContributionGraph
className="w-full max-w-sm"
label="Five week activity"
data={data}
levels={3}
/>
)
}API reference
| Prop | Type | Default |
|---|---|---|
data*Daily values in calendar order. | ContributionDay[] | — |
label*Accessible description of the measure. | string | — |
levelsNumber of color levels for nonzero values. | 2 | 3 | 4 | 4 |
Also accepts the standard attributes of its root element.
Accessibility
| Key | Action |
|---|---|
| Tab / Arrow keys | Focuses and scrolls the heatmap; total and date span are announced |
Use with AI
A compact daily activity heatmap with token-based intensity and an accessible summary. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Contribution Graph (@ballmac/contribution-graph) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Show a daily activity pattern over time
Not for
- Use a calendar when dates need interaction
Registry JSON: https://ui.ballmac.com/r/contribution-graph.json
Credits
Free to use in personal and commercial projects.
- npm
- None
- Registry
- @ballmac/i18nshadcn/utils
Pairs well with
Activity Feed
A compact actor and action feed with timestamps and an empty state.
Audio Player
A podcast-style audio player: play and pause, skip back and forward, a scrubber with chapter ticks, a named current chapter and a speed control. Works with a real file or as a silent demo.
Avatar Circles
Overlapping avatars that spread on hover, lift individually with a name and role tooltip, show presence, fall back to initials and end in a +N count that can be a button.
Avatar Stack
An accessible compact row of people with initials, image support, and an overflow count.