Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelog
  1. Home
  2. Blog
  3. Component guides
  4. React GitHub Contribution Graph: Data, Themes and A11y
Auf Deutsch lesen
  1. Blog
  2. Component guides
  3. React GitHub Contribution Graph: Data, Themes and A11y
Component guides
Component guides

React GitHub Contribution Graph: Data, Themes and A11y

SR
Serban Rusu · Founder of Wingo UI
Oct 19, 2026 · 9 min read

On this page

0%
  1. Which React contribution graph packages exist?
  2. How do I add a GitHub contribution graph React component?
  3. How do I render the contribution graph on the server?
  4. How do I theme the contribution colors for light and dark mode?
  5. When do I need a general activity heatmap in React?
  6. How do I make a react calendar heatmap accessible?
  7. How does the heatmap behave on a phone?
  8. What are the common contribution graph pitfalls?
  9. How do I add these to my project?
  10. FAQ
    1. How do I show my GitHub contribution graph in React?
    2. Where does contribution graph data come from?
    3. What is the difference between a contribution graph and a calendar heatmap?
    4. Is a heatmap calendar accessible?
    5. Is the Wingo UI GitHub Activity free?

TL;DR

For a portfolio or profile page, the free Wingo UI GitHub Activity renders a React GitHub contribution graph from a username or from data you fetch on the server, and fits its weeks to the screen so it never scrolls sideways. For your own product data (orders, workouts, CRM activity), the Pro Heatmap Calendar adds keyboard navigation, a spoken label per day, a screen reader table and a one-hue scale that stays readable in light and dark mode.

Published Oct 19, 2026

You want a React GitHub contribution graph on a portfolio, an "about" page or a developer profile: the familiar grid of green squares, one per day, with the yearly total on top. Or you want the same shape for your own data, like orders per day, team activity in a CRM or workout streaks. The first case is mostly about where the data comes from. The second is about scales, labels and keyboard access, because a grid of colored squares tells a screen reader nothing. This guide covers both with two Wingo UI components: the free GitHub Activity and the Pro Heatmap Calendar.

Which React contribution graph packages exist?

Three established npm packages draw this grid: react-activity-calendar, react-github-calendar and react-calendar-heatmap, all MIT licensed. Versions are from the npm registry as of October 19, 2026.

PackageVersionWhat it doesNotes from its README
react-activity-calendar (opens in a new tab)3.2.1Draws the grid from { date, count, level } dataAny number of levels (including negative ones), color themes, dark and light mode, tooltips, localization, SSR supported
react-github-calendar (opens in a new tab)5.0.9Takes a username and fetches the calendarFetches in the browser, so the README says it does not render on the server; it points to react-activity-calendar for SSR
react-calendar-heatmap (opens in a new tab)1.10.0SVG heatmap from { date, count }You map values to CSS classes with classForValue and style them yourself; tooltips through titleForValue or data attributes

None of the three READMEs mentions keyboard navigation or screen reader output (checked October 19, 2026). If you want a packaged widget and can live with that, react-activity-calendar is the most complete. If you want source you can edit, with the same props, theming and motion as the rest of your UI, the two components below install into components/ui.

How do I add a GitHub contribution graph React component?

Install GitHub Activity with the CLI and pass it a GitHub username:

bash
npx wingo-ui@latest add github-activity
tsx
import GitHubActivity from "@/components/ui/github-activity";
export function Profile() {
return <GitHubActivity username="octocat" showMonths />;
}

With a username, the component loads two things in the browser. The calendar comes from github-contributions-api (opens in a new tab), a free service that reads the public profile page; its README says results are cached for one hour and uncached requests are limited to 10 per 10 seconds per IP. The top repositories in the footer come from GitHub's public events endpoint, which only returns events from the past 30 days and at most 300 of them (opens in a new tab), and unauthenticated REST calls are limited to 60 requests per hour (opens in a new tab). Fine for a personal page; for anything with traffic, fetch on the server (next section).

An invalid or unknown login shows "No public activity found for {login}" (the notFoundLabel template, with the login filled in) and calls onError, so you can fall back to stored data.

The demo below uses the sample year that ships with the docs, so no request goes to GitHub.

Hover a square, or press one on a touchscreen, to read its count and date. Click the chevron to expand the top repositories over the grid, then click it again to fold them back
$ npx wingo-ui@latest add github-activity
FreeGitHub Activity docs

How do I render the contribution graph on the server?

Fetch the calendar from GitHub's GraphQL API in a server component and pass it as contributions. When you pass the data and no username, nothing is fetched in the browser, the token stays on the server, and the first HTML already has the right total. The GraphQL reference (opens in a new tab) describes contributionCalendar.weeks[].contributionDays[] with a date, a contributionCount and a contributionLevel from NONE to FOURTH_QUARTILE; without from and to, the collection covers the last year.

tsx
import GitHubActivity, { type Contribution, type ContributionLevel } from "@/components/ui/github-activity";
const LEVELS: Record<string, ContributionLevel> = {
NONE: 0,
FIRST_QUARTILE: 1,
SECOND_QUARTILE: 2,
THIRD_QUARTILE: 3,
FOURTH_QUARTILE: 4,
};
const QUERY = `query ($login: String!) {
user(login: $login) {
contributionsCollection {
contributionCalendar {
weeks { contributionDays { date contributionCount contributionLevel } }
}
}
}
}`;
type Day = { date: string; contributionCount: number; contributionLevel: string };
async function getContributions(login: string): Promise<Contribution[]> {
const res = await fetch("https://api.github.com/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query: QUERY, variables: { login } }),
cache: "force-cache",
next: { revalidate: 3600 },
});
if (!res.ok) return [];
const json = await res.json();
const weeks: { contributionDays: Day[] }[] =
json.data?.user?.contributionsCollection.contributionCalendar.weeks ?? [];
const days = weeks.flatMap((week) => week.contributionDays);
// one column per week, so the data has to start on a sunday
const start = days.findIndex((day) => new Date(`${day.date}T00:00:00Z`).getUTCDay() === 0);
return days.slice(Math.max(0, start)).map((day) => ({
date: day.date,
count: day.contributionCount,
level: LEVELS[day.contributionLevel] ?? 0,
}));
}
export async function Contributions() {
const contributions = await getContributions("your-login");
if (contributions.length === 0) return null;
return <GitHubActivity contributions={contributions} yearLabel="in the last year" showMonths className="max-w-2xl" />;
}

Two details. Caching is opt-in in Next.js 16, and cache: "force-cache" is what caches a POST that sends an authorization header (fetch docs (opens in a new tab)); revalidate: 3600 caps the cached copy at one hour. And the heading reads "{count} contributions in {year}" by default, with the year of the newest day; for a rolling window, yearLabel="in the last year" is more accurate. Pass repos as { name, count, href?, logo? }[] if you want the footer panel too.

How do I theme the contribution colors for light and dark mode?

Pass the colors through accent, as one color or an array of shades. One color (#39d353 by default) is drawn at 30%, 52%, 76% and 100% opacity over an empty cell that is the foreground at 8%, so the same value works on the white card in light mode and the black card in dark mode. An array of four shades sets each level exactly, and five shades also replace the empty color:

tsx
<GitHubActivity contributions={contributions} accent={["#0e4429", "#006d32", "#26a641", "#39d353"]} />

Those four greens are tuned for a dark background. If your shades differ per theme, pass CSS variables and define them for each theme in your stylesheet, for example accent={["var(--commit-1)", "var(--commit-2)", "var(--commit-3)", "var(--commit-4)"]}. The Tailwind v4 dark mode guide shows where those variables go. background, radius, cellSize (11px), cellRadius (3px) and chevronColor cover the rest of the look.

On a phone, the grid shows as many recent weeks as fit the card's width and drops older ones, so it never scrolls sideways. months caps the history, and a max-w-* class shows fewer weeks on purpose.

When do I need a general activity heatmap in React?

As soon as the data is yours. A GitHub graph assumes the levels are already computed and the reader only needs a feel for the year. A dashboard tile with orders, invoices or support tickets per day needs more: your own thresholds, your own nouns in the labels, a selected day that drives a list, and real numbers for people who cannot see the colors. That is the job of an activity heatmap react component like the Heatmap Calendar.

bash
npx wingo-ui@latest add heatmap-calendar
tsx
"use client";
import { useState } from "react";
import { HeatmapCalendar, type HeatmapDatum } from "@/components/ui/heatmap-calendar";
export function WorkoutHeatmap({ data }: { data: HeatmapDatum[] }) {
const [day, setDay] = useState<string | null>(null);
return (
<HeatmapCalendar
data={data}
weeks={26}
tone="success"
thresholds={[1, 20, 40, 60]}
value={day}
onValueChange={setDay}
locale="en-US"
label="Workout minutes per day"
labels={{
total: (n, weeks) => `${n} minutes in the last ${weeks} weeks`,
cell: (value, date) => `${value} minutes on ${date}`,
empty: (date) => `No workout on ${date}`,
valueName: "Minutes",
table: "Workout minutes per month",
}}
/>
);
}

data is one { date, value } per ISO day; missing days count as 0 and duplicate dates add up. Without thresholds, the levels come from quantiles of the non-zero values, so any activity is at least level 1. endDate defaults to the newest date in your data, never the clock, so the server and the browser draw the same grid. weekStartsOn follows the locale (Sunday in en-US, Monday in en-GB) unless you set it.

Hover a day for its readout, click it to select it, then Tab into the grid and move with the arrow keys. Switch between All, Calls, Emails and Meetings to see the colors crossfade
$ npx wingo-ui@latest add heatmap-calendar
ProHeatmap calendar docs

How do I make a react calendar heatmap accessible?

Make every number available without color. WCAG success criterion 1.4.1 Use of Color (opens in a new tab) asks that color is not the only way information is conveyed, and a heatmap is exactly that by default. In 2018 Adrian Roselli reviewed GitHub's contributions chart (opens in a new tab), found it unusable by keyboard and invisible to screen readers, and argued the data belongs in a table. GitHub fixed its own graph in March 2023 (opens in a new tab): arrow keys move between days, and a screen reader hears the graph as a table and a summary of each day.

The Heatmap Calendar follows the same model:

  • One Tab stop. Tab lands on the selected day or the newest one. Left and Right move by a week, Up and Down by a day, Home and End jump to the first and last day, PageUp and PageDown move four weeks, Enter or Space selects, Escape clears.
  • A spoken label per day from labels.cell and labels.empty, such as "42 minutes on Thursday, September 24, 2026" in en-US, with the date written out for your locale.
  • A screen reader table with each month's total and active days, captioned by label. description adds a one-sentence takeaway.
  • A scale that reads in both themes. Level 0 is the foreground at 7%, visible on white and on the dark surface. The other levels are one hue from 30% to 100% in equal steps, so the order is carried by lightness. Set tone, or any CSS color with color, and emptyColor for the empty days.

GitHub Activity is lighter here. Its grid is one image named after the heading ("1234 contributions in the last year"), and the per-day tooltip answers hover and touch, not the keyboard. That suits a decorative profile card. When the daily numbers matter, use the Heatmap Calendar.

How does the heatmap behave on a phone?

On a phone, the Heatmap Calendar either scrolls the year as a horizontal rail or shows only the weeks that fit, depending on overflow. overflow="scroll" (the default) turns the year into a horizontal rail that opens at the newest week, snaps by week, fades at the edges and keeps the weekday labels pinned; vertical drags still scroll the page. overflow="fit" shows only the newest weeks that fit, with nothing to scroll.

Touch has its own rules: the first tap on a day pins its readout above the grid (so a finger never covers it), a second tap on the same day selects it, and a tap outside clears it. tooltipMode defaults to "auto", which uses that fixed readout on touch screens and a floating one next to the day elsewhere. The intro sweep and the color crossfade respect reduced motion, as explained in the prefers-reduced-motion guide.

What are the common contribution graph pitfalls?

These four break a contribution graph in ways that are easy to miss in development:

  • Data that does not start on a Sunday. GitHub Activity draws one column per seven entries of the contributions you pass, and only trims its own fetched data. Start the array on a Sunday or every column shears. The server example above trims the first partial week.
  • Calling GitHub from every visitor's browser. The free APIs have per-IP limits and the events endpoint only covers 30 days. Fetch on the server and cache it.
  • Reading "today" during render. The Heatmap Calendar reads the viewer's date only after hydration for the today ring and uses your data for endDate. Keep it that way in your own wrappers, or the server and client grids disagree.
  • A heatmap as the only view of a KPI. Pair it with a number: a Stat Card for the total or a Sparkline for the trend. The dashboard library comparison covers the rest of a dashboard.

How do I add these to my project?

Run npx wingo-ui@latest add github-activity for the free contribution graph, or npx wingo-ui@latest add heatmap-calendar for the general heatmap (Pro, see pricing). The CLI copies the source into components/ui and adds what it imports. More single-component deep dives live in the component guides category, and the React form components guide applies the same keyboard and screen reader rules to every field.

Components in this post

  • GitHub Activity

    A React GitHub contribution graph: a yearly heatmap with a panel of your top repositories.

    Free
  • Heatmap calendar

    A React heatmap calendar of activity by day over weeks or a year, on a one-hue scale with month and weekday labels, a total and a legend.

    Pro

FAQ

How do I show my GitHub contribution graph in React?

Install a component that draws the yearly grid and give it either a username or the daily data. With Wingo UI, loads the last year in the browser, or you fetch the calendar on the server with the GitHub GraphQL API and pass it as contributions.

Where does contribution graph data come from?

GitHub's GraphQL API exposes it as user.contributionsCollection.contributionCalendar, with a count and a level for each day; it needs a token. Without a token, react-github-calendar and the Wingo UI component's username mode call the free github-contributions-api, which reads the public profile page and caches results for one hour.

What is the difference between a contribution graph and a calendar heatmap?

A contribution graph is a calendar heatmap with GitHub's data and look: weeks as columns, days as rows, five shades of green. A general react calendar heatmap takes any daily value, such as orders or minutes, and lets you choose the scale, the colors and the labels.

Is a heatmap calendar accessible?

Only if the numbers are available without color. Give each day a text label, make the grid reachable by keyboard, and offer a table or a summary for screen readers, which is what GitHub added to its own graph in March 2023.

Is the Wingo UI GitHub Activity free?

Yes. GitHub Activity is in the free tier and installs with npx wingo-ui@latest add github-activity. The Heatmap Calendar is a Pro component.

  • Charts
  • Heatmap
  • GitHub
  • Accessibility
  • React

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Component guides

Embla Carousel React: Accessible Slides, No Layout Shift

Build an embla carousel react component for photos, listings, testimonials and onboarding, with keyboard controls, pausable autoplay and no layout shift.

SRSerban Rusu·Oct 19, 2026·9 min read
Component guides

React Animated Number Counter: Rolling Odometer Digits

Build a React animated number counter that rolls each digit like an odometer, takes its separators from Intl, holds its width and respects reduced motion.

SRSerban Rusu·Oct 19, 2026·9 min read
Component guides

React Audio Player with Waveform: Scrub, Seek, Peaks

Build a React audio player with waveform scrubbing for podcasts, chat voice notes and call logs, with keyboard seek, precomputed peaks and no layout shift.

SRSerban Rusu·Oct 19, 2026·9 min read
Newsletter

Get new posts by email

New guides, tutorials and comparisons from the Wingo UI blog, sent when they are published.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • Theme generator

Resources

  • Docs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI