
Security News
GitHub Actions Checkout Now Blocks Risky pull_request_target Checkouts
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.
github-contrib-graph
Advanced tools
Lightweight, customizable GitHub contribution graph widget for any website
A lightweight, customizable GitHub contribution graph widget for React, vanilla JavaScript, and plain HTML pages.
Package name:
github-contrib-graph. Repository name:github-contribution-graph. The npm package namedgithub-contribution-graphis a different package.
npm install github-contrib-graph
import { GitHubContributionGraph } from 'github-contrib-graph/react';
import 'github-contrib-graph/styles.css';
export function ProfileActivity() {
return (
<GitHubContributionGraph
username="octocat"
theme="midnight"
onDataLoaded={(data) => {
console.log(data.contributionsCollection.contributionCalendar.totalContributions);
}}
/>
);
}
import { GitHubContributionWidget } from 'github-contrib-graph/vanilla';
import 'github-contrib-graph/styles.css';
const widget = new GitHubContributionWidget({
username: 'octocat',
container: '#my-graph',
theme: 'void',
});
await widget.render();
<div id="my-graph"></div>
Use this when you do not have a bundler.
<link rel="stylesheet" href="https://unpkg.com/github-contrib-graph@latest/dist/gh.css">
<div
id="gh"
data-login="octocat"
data-show-thumbnail="true"
data-show-header="true"
data-show-footer="true"
></div>
<script src="https://unpkg.com/github-contrib-graph@latest/dist/browser.global.js"></script>
For production pages, pin a version instead of using @latest:
<link rel="stylesheet" href="https://unpkg.com/github-contrib-graph@3.1.1/dist/gh.css">
<script src="https://unpkg.com/github-contrib-graph@3.1.1/dist/browser.global.js"></script>
The browser bundle auto-renders an element with id="gh" and data-login. It also exposes window.renderGitHubWidget() for manual re-rendering after you change data-login.
| Attribute | Default | Description |
|---|---|---|
data-login | required | GitHub username to render |
data-show-thumbnail | "true" | Show or hide the GitHub attribution icon |
data-show-header | "true" | Show or hide the contribution total and avatar |
data-show-footer | "true" | Show or hide the Less/More legend |
import { GitHubContributionGraph } from 'github-contrib-graph/react';
<GitHubContributionGraph
username="octocat"
apiEndpoint="https://your-domain.com/api/ghcg/fetch-data"
theme="default"
showHeader={true}
showFooter={true}
showThumbnail={true}
showMonthLabels={true}
showWeekdayLabels={true}
showTooltips={true}
dayLabels={['', 'Mon', '', 'Wed', '', 'Fri', '']}
footerLabels={{ less: 'Less', more: 'More' }}
classNames={{ root: 'my-graph-root', dayCell: 'my-day-cell' }}
dayClassName={({ day }) => (day.contributionCount > 0 ? 'has-activity' : '')}
dayStyle={({ day }) => ({
opacity: day.contributionCount === 0 ? '0.45' : '1',
})}
dayAttributes={({ day }) => ({
'aria-label': `${day.contributionCount} contributions on ${day.date}`,
})}
tooltipFormatter={({ day, date }) =>
`${day.contributionCount} contributions on ${date.toLocaleDateString()}`
}
monthLabelFormatter={(month) => month.name.slice(0, 3)}
className="my-graph"
style={{ margin: 20 }}
onDataLoaded={(data) => {}}
onError={(error) => {}}
onLoading={(isLoading) => {}}
/>;
useContributionDataimport { useContributionData } from 'github-contrib-graph/react';
function TotalContributions() {
const { data, loading, error, refetch } = useContributionData('octocat', {
autoFetch: true,
});
if (loading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
return (
<button onClick={refetch}>
{data?.contributionsCollection.contributionCalendar.totalContributions}
</button>
);
}
import { GitHubContributionWidget } from 'github-contrib-graph/vanilla';
const widget = new GitHubContributionWidget({
username: 'octocat',
container: '#my-graph',
apiEndpoint: 'https://your-domain.com/api/ghcg/fetch-data',
theme: 'void',
showHeader: true,
showFooter: true,
showThumbnail: true,
showMonthLabels: true,
showWeekdayLabels: true,
showTooltips: true,
classNames: {
root: 'profile-graph',
dayCell: 'profile-graph__day',
},
dayStyle: ({ day }) => ({
transform: day.contributionCount > 20 ? 'scale(1.15)' : 'scale(1)',
}),
onDataLoaded: (data) => {},
onError: (error) => {},
});
await widget.render();
await widget.refresh();
const data = widget.getData();
widget.destroy();
await widget.update({ username: 'another-user' });
Built-in presets:
defaultvoidslatemidnightglaciercyberPass a preset name:
<GitHubContributionGraph username="octocat" theme="cyber" />
Or pass a custom theme object:
const widget = new GitHubContributionWidget({
username: 'octocat',
theme: {
bgColor: '#111111',
textColor: '#eeeeee',
cellLevel0: '#222222',
cellLevel1: '#0e4429',
cellLevel2: '#006d32',
cellLevel3: '#26a641',
cellLevel4: '#39d353',
borderColor: '#333333',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
},
});
You can also override CSS variables directly on the widget root:
.my-graph {
--gh-bg-color: #000000;
--gh-text-default-color: #ffffff;
--gh-cell-level0-color: #111111;
--gh-border-card-color: #333333;
--gh-font-default-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
The rendered root receives the ghContributionGraph class, so styles work even when your container is not #gh.
The package has layered customization. You can use only the built-in presets, override the default DOM with class and render hooks, or skip the default DOM entirely in React and render your own calendar from the fetched data.
Every theme key can be passed through the theme prop or config option. String values are used as-is. Numeric values are converted to px.
<GitHubContributionGraph
username="octocat"
theme={{
bgColor: '#080c10',
textColor: '#f4f7fb',
inactiveTextColor: '#7d8794',
linkHoverColor: '#8ab4ff',
cellLevel0: '#18202a',
cellLevel1: '#163b2a',
cellLevel2: '#196c3d',
cellLevel3: '#32a852',
cellLevel4: '#7ee787',
cellSize: 13,
cellGap: 4,
cellRadius: 4,
cellBorderColor: 'rgba(255,255,255,0.08)',
cellOutlineColor: 'transparent',
tooltipBgColor: '#f4f7fb',
tooltipTextColor: '#080c10',
tooltipPadding: '8px 10px',
tooltipRadius: 8,
tooltipFontSize: 12,
borderColor: '#27313d',
borderWidth: 1,
cardPadding: 18,
cardPaddingBlock: 10,
cardRadius: 10,
canvasPaddingTop: 8,
canvasMarginInline: 12,
headerHeight: 28,
headerMarginBottom: 8,
headerFontSize: 14,
avatarSize: 24,
footerPadding: '8px 32px',
footerFontSize: 12,
fontFamily: 'Inter, ui-sans-serif, system-ui, sans-serif',
}}
/>
The matching CSS variables are:
| Theme key | CSS variable |
|---|---|
bgColor | --gh-bg-color |
textColor | --gh-text-default-color |
inactiveTextColor | --gh-text-inactive-color |
linkHoverColor | --gh-link-hover-color |
cellLevel0 - cellLevel4 | --gh-cell-level0-color - --gh-cell-level4-color |
cellSize | --gh-cell-size |
cellGap | --gh-cell-gap |
cellRadius | --gh-cell-radius |
cellBorderColor | --gh-cell-border-color |
cellOutlineColor | --gh-cell-outline-color |
tooltipBgColor | --gh-cell-info-bg-color |
tooltipTextColor | --gh-tooltip-text-color |
tooltipPadding | --gh-tooltip-padding |
tooltipRadius | --gh-tooltip-radius |
tooltipFontSize | --gh-tooltip-font-size |
borderColor | --gh-border-card-color |
borderWidth | --gh-border-card-width |
cardPadding | --gh-card-padding |
cardPaddingBlock | --gh-card-padding-block |
cardRadius | --gh-card-radius |
canvasPaddingTop | --gh-canvas-padding-top |
canvasMarginInline | --gh-canvas-margin-inline |
headerHeight | --gh-header-height |
headerMarginBottom | --gh-header-margin-bottom |
headerFontSize | --gh-header-font-size |
avatarSize | --gh-avatar-size |
footerPadding | --gh-footer-padding |
footerFontSize | --gh-footer-font-size |
fontFamily | --gh-font-default-family |
<GitHubContributionGraph
username="octocat"
showMonthLabels
showWeekdayLabels
showTooltips
dayLabels={['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']}
footerLabels={{ less: 'Quiet', more: 'Busy' }}
classNames={{
root: 'activity',
header: 'activity__header',
total: 'activity__total',
profile: 'activity__profile',
profileLink: 'activity__profile-link',
avatar: 'activity__avatar',
card: 'activity__card',
canvas: 'activity__canvas',
table: 'activity__table',
monthLabel: 'activity__month',
dayLabel: 'activity__weekday',
dayCell: 'activity__day',
tooltip: 'activity__tooltip',
footer: 'activity__footer',
footerLegend: 'activity__legend',
thumbnail: 'activity__thumbnail',
thumbnailLink: 'activity__thumbnail-link',
}}
dayClassName={({ day }) =>
day.contributionCount >= 10 ? 'activity__day--hot' : undefined
}
dayStyle={({ day }) => ({
opacity: day.contributionCount === 0 ? '0.35' : '1',
borderRadius: day.contributionCount >= 10 ? 6 : 2,
})}
dayAttributes={({ day }) => ({
'aria-label': `${day.contributionCount} contributions on ${day.date}`,
'data-busy': day.contributionCount >= 10,
})}
tooltipFormatter={({ day, date, username }) =>
`${username} made ${day.contributionCount} contributions on ${date.toLocaleDateString()}`
}
monthLabelFormatter={(month) => month.name.toUpperCase()}
/>
Every day cell also receives these default attributes, which makes CSS-only custom designs straightforward:
.activity__day[data-level='FOURTH_QUARTILE'] {
box-shadow: 0 0 0 2px color-mix(in srgb, var(--gh-cell-level4-color), white 20%);
}
.activity__day[data-count='0'] {
opacity: 0.4;
}
The default renderer can replace the header, footer, thumbnail, or the contents inside each day cell. These hooks return DOM nodes or strings.
const widget = new GitHubContributionWidget({
username: 'octocat',
container: '#my-graph',
renderHeader: ({ username, totalContributions, user }) => {
const header = document.createElement('header');
header.className = 'activity-header';
const avatar = document.createElement('img');
avatar.src = user.avatarUrl;
avatar.alt = `${username}'s avatar`;
const name = document.createElement('strong');
name.textContent = username;
const total = document.createElement('span');
total.textContent = `${totalContributions.toLocaleString()} contributions`;
header.append(avatar, name, total);
return header;
},
renderDayContents: ({ day }) => {
const dot = document.createElement('span');
dot.className = 'activity-dot';
dot.textContent = day.contributionCount > 0 ? String(day.contributionCount) : '';
return dot;
},
renderFooter: ({ labels }) => {
const footer = document.createElement('footer');
footer.textContent = `${labels.less} / ${labels.more}`;
return footer;
},
renderThumbnail: () => null,
});
When using the React component, renderHeader, renderFooter, renderThumbnail, and renderDayContents still belong to the default DOM renderer, so they return DOM nodes. Use the React render prop if you want JSX-level control.
The render prop lets React users use the package only for fetching and state management. You receive the raw GitHub contribution calendar and can render every pixel yourself.
<GitHubContributionGraph
username="octocat"
render={({ data, loading, error, refresh }) => {
if (loading) return <p>Loading activity...</p>;
if (error) return <button onClick={refresh}>Retry</button>;
const calendar = data?.contributionsCollection.contributionCalendar;
return (
<section className="activity-board">
<strong>{calendar?.totalContributions.toLocaleString()} contributions</strong>
<div className="activity-grid">
{calendar?.weeks.flatMap((week) =>
week.contributionDays.map((day) => (
<span
key={day.date}
className="activity-cell"
data-level={day.contributionLevel}
title={`${day.contributionCount} on ${day.date}`}
/>
))
)}
</div>
</section>
);
}}
/>
By default, the package fetches contribution data from:
https://githubgraph.jigyansurout.com/api/ghcg/fetch-data?login={username}
The response shape matches the GitHub GraphQL contribution calendar:
{
"user": {
"avatarUrl": "https://avatars.githubusercontent.com/...",
"contributionsCollection": {
"contributionCalendar": {
"totalContributions": 1234,
"months": [],
"weeks": []
}
}
}
}
Use apiEndpoint if you want to run your own backend.
This repository includes a Cloudflare Pages Functions backend at functions/api/ghcg/fetch-data.js.
GITHUB_TOKEN to your Cloudflare Pages environment variables.npm run build as the build command.cf-dist as the Pages output directory.functions/.<GitHubContributionGraph
username="octocat"
apiEndpoint="https://your-domain.com/api/ghcg/fetch-data"
/>
Local development:
npm install
echo "GITHUB_TOKEN=your_token" > .env
npm run build
npm run dev
github-contrib-graph, not github-contribution-graph, when installing from npm.<script> after the graph container or call window.renderGitHubWidget() after adding the container.data-login or username is a valid GitHub username.{ "user": ... } and includes CORS headers for browser usage.Apache-2.0
FAQs
Lightweight, customizable GitHub contribution graph widget for any website
The npm package github-contrib-graph receives a total of 4,436 weekly downloads. As such, github-contrib-graph popularity was classified as popular.
We found that github-contrib-graph demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.