contribution-graph-3d
v0.1.1
Published
A dynamic, drag-to-orbit 3D WebGL skyline render of a GitHub contribution graph, with a GitHub-style 2D grid and a framework-agnostic data fetcher.
Maintainers
Readme
contribution-graph-3d
A drag-to-orbit 3D WebGL skyline render of a GitHub contribution graph, built on three.js. Ships with the classic GitHub-style 2D grid (that swaps to 3D on click) and a framework-agnostic helper for fetching a user's contribution calendar from GitHub.

It grows out of a flat GitHub-style grid, which is what renders first:

See it running on my site at strydr.net (click the grid on desktop to enter 3D).
- 3D skyline: every active day is a pillar whose height and color track its contribution count. Animated entrance, on-demand rendering, drag to orbit.
- 2D grid: GitHub-style weekday × week layout on desktop, a transposed month-by-day layout on mobile. No CSS framework required.
- Data fetcher: a tiny, dependency-free function over GitHub's GraphQL API.
- Themeable: the whole 5-step color ramp and proportions are props.
threeis dynamically imported, so it never touches your initial bundle.
Install
npm install contribution-graph-3d three reactreact (>=18) and three (>=0.150) are peer dependencies, so bring your own.
Quick start
1. Fetch the data (server-side)
fetchContributions needs a GitHub token, so it must run on a server. Here's a
Next.js App Router route handler:
The fetcher lives on the contribution-graph-3d/server subpath, kept separate
from the React components so importing it into a server route never pulls a
'use client' module into your server bundle.
// app/api/contributions/route.ts
import { NextResponse } from 'next/server'
import { fetchContributions } from 'contribution-graph-3d/server'
export const dynamic = 'force-dynamic'
export async function GET() {
const data = await fetchContributions({
login: 'strvdr',
token: process.env.GITHUB_TOKEN!,
})
return NextResponse.json(data)
}The token only needs read:user scope. A classic PAT or a fine-grained token
both work. Never expose it to the browser.
2. Render the widget
'use client'
import { ContributionGraph } from 'contribution-graph-3d'
export function Profile() {
// Fetches /api/contributions on mount; click the desktop grid to go 3D.
return <ContributionGraph />
}That's it. The flat grid renders immediately and swaps to the 3D skyline when clicked (Esc to exit).
Usage patterns
Bring your own data
Skip the internal fetch by passing data directly (server component → client):
import { ContributionGraph } from 'contribution-graph-3d'
import { fetchContributions } from 'contribution-graph-3d/server'
const data = await fetchContributions({ login: 'strvdr', token })
return <ContributionGraph data={data} />Just the 3D renderer
If you only want the skyline, use ContributionGraph3D with the weeks array:
import { ContributionGraph3D } from 'contribution-graph-3d'
<ContributionGraph3D weeks={data.weeks} height={400} />Custom endpoint
<ContributionGraph fetchUrl="/my/contributions.json" />Theming
Every color and proportion has a default (the "Tokyo Night" palette). Override
any subset via the theme prop; unset fields fall back to the defaults:
<ContributionGraph
theme={{
// Five-step intensity ramp: [empty, low, …, high]
levels: ['#161b22', '#0e4429', '#006d32', '#26a641', '#39d353'],
heightScale: 2.2, // taller skyline
labelColor: '#8b949e',
}}
/>| Field | Applies to | Default | Meaning |
| ------------------- | ---------- | ------------------------ | ----------------------------------------- |
| levels | 2D + 3D | Tokyo Night green ramp | 5-step ramp, index 0 = empty, 4 = most |
| labelColor | 3D | #7982a9 | Axis-label text |
| labelOpacity | 3D | 0.95 | Axis-label opacity when faded in |
| heightScale | 3D | 1.9 | Vertical exaggeration of the skyline |
| gridLineColor | 3D | #363b54 | Empty-day outlines on the ground plane |
| shadowOpacity | 3D | 0.28 | Drop-shadow strength |
| monthLabelColor | 2D | #414868 | Month axis labels |
| weekdayLabelColor | 2D | #414868 | Weekday axis labels |
| captionColor | 2D | #414868 | Legend + caption text |
| cellOutline | 2D | rgba(65,72,104,0.15) | Per-cell outline (any CSS color) |
| promptSymbolColor | 2D | #7aa2f7 | ❯ prompt glyph |
| promptTextColor | 2D | #c0caf5 | git log … header text |
| countTextColor | 2D | #565f89 | "N contributions in the last year" text |
defaultTheme and resolveTheme(partial) are exported if you want to inspect or
compose themes yourself.
API
<ContributionGraph />
The full widget: 2D grid, responsive fallback, and click-to-3D.
| Prop | Type | Default | Notes |
| ---------- | --------------------------- | ---------------------- | ------------------------------------------------ |
| data | Contributions | none | Pre-fetched calendar. If omitted, fetches. |
| fetchUrl | string | /api/contributions | JSON endpoint, used only when data is unset. |
| theme | Partial<ContributionTheme>| default palette | Color / proportion overrides. |
| enable3D | boolean | true | Allow click-to-3D on desktop. |
| height | number | 340 | 3D canvas height in px. |
<ContributionGraph3D />
The standalone skyline.
| Prop | Type | Default | Notes |
| --------- | ---------------------------- | ------- | ---------------------------------- |
| weeks | Week[] | (req.) | Required. One entry per week. |
| theme | Partial<ContributionTheme> | default | Color / proportion overrides. |
| height | number | 340 | Canvas height in px. |
| onReady | () => void | none | Fired when the entrance completes. |
fetchContributions(options)
Imported from contribution-graph-3d/server. Returns Promise<Contributions>.
Server-side only.
| Option | Type | Default | Notes |
| ----------- | ------------------- | ----------------------- | ---------------------------------------- |
| login | string | (req.) | Required. GitHub handle. |
| token | string | (req.) | Required. Token with read:user scope. |
| userAgent | string | contribution-graph-3d | Sent as User-Agent. |
| fetch | typeof fetch | global fetch | Inject a custom fetch. |
Types
interface Day { date: string; count: number; weekday: number }
type Week = Day[]
interface Contributions { totalContributions: number; weeks: Week[] }date is YYYY-MM-DD; weekday is 0 (Sun) to 6 (Sat).
Accessibility & performance
- Honors
prefers-reduced-motion: skips the entrance animation and renders the resting skyline directly. - The 3D canvas renders on demand (one frame per drag event, not a continuous render loop), so it costs nothing while idle.
threeloads via dynamicimport(), keeping it out of your initial JS bundle.
Development
npm install
npm run build # bundle to dist/ (ESM + CJS + types) via tsup
npm run typecheck # tsc --noEmitLicense
MIT © Strydr Silverberg
