npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

contribution-bubbles

v0.1.0

Published

A GitHub contribution chart drawn as bubbles. One line of React, just a username.

Readme

contribution-bubbles

Your GitHub contributions as bubbles. One line of React, just a username.

contribution-bubbles preview

npm install contribution-bubbles
import { ContributionBubbles } from "contribution-bubbles";

<ContributionBubbles username="octocat" />

That's it. It loads the past year from GitHub, fills its container and draws one column per week, one row per weekday. Busier days get bigger, stronger bubbles.

No API key, no login, no CSS to import. Works in any React 18+ app, including Next.js.

Size and color

<ContributionBubbles username="octocat" width={400} color="#30a14e" />

Leave out width to fill the parent. The width decides how many weeks fit, about a year in 400px. To pick the number yourself, use weeks.

<ContributionBubbles username="octocat" weeks={26} />

Next.js

The component works in server pages as is. For the best performance, load the data on the server and cache it, so visitors never wait on GitHub.

// app/page.tsx
import { ContributionBubbles } from "contribution-bubbles";
import { fetchContributions } from "contribution-bubbles/server";

export default async function Page() {
  const days = await fetchContributions("octocat", { next: { revalidate: 3600 } });
  return <ContributionBubbles data={days} />;
}

Loading and errors

While a username loads, the chart keeps its space so the page doesn't jump. If loading fails (a typo in the username, say), the chart shows nothing and logs why to the console.

<ContributionBubbles
  username="octocat"
  loading={<p>Loading activity…</p>}
  fallback={<p>No activity yet</p>}
  onError={(error) => report(error)}
/>

All props

Every prop is optional except one of username or data.

| Prop | Default | | | --- | --- | --- | | username | | GitHub username to load | | data | | Your own days, [{ date: "2026-01-31", count: 4 }]. Wins over username | | width | fills parent | Width in px | | weeks | fits the width | Number of weeks to show | | cellSize | 7.5 | Width of a week in px, when weeks isn't set | | color | #30a14e | Dot color. Any CSS color or variable | | opacity | [0.3, 0.9] | Opacity of the quietest and busiest days | | steps | 4 | Opacity levels, like GitHub's. 0 for a smooth fade | | minSize | 2.4 | Smallest dot, diameter in px | | maxSize | 14.3 | Biggest dot, diameter in px | | showMonths | true | Month names on top | | showTotal | true | "1,234 contributions in the past year" below | | font | system mono | Font for both labels, any CSS font-family | | monthFont | font | Font for the month names | | totalFont | font | Font for the total line | | monthSize | 10 | Month name size in px | | totalSize | monthSize | Total line size in px | | uppercase | true | Labels in capitals | | labelColor | inherits | Label color | | monthGap | 2 | Space under the month names in px | | totalAlign | "left" | "left", "center" or "right" | | formatTotal | | Your own wording, (n) => \${n} commits`| |locale|"en-US"| Language for month names and numbers | |loading| empty space | Shown while a username loads | |fallback| nothing | Shown on error or when there's no activity | |onError| logs a warning | Called when loading fails | |label| the total | What screen readers announce | |className, style| | For the outer element | |classNames, styles` | | Per part, see below |

Customize anything

Every part takes its own class name and inline style, so you can restyle it with your own CSS or Tailwind. The parts are root, months, month, chart and total.

<ContributionBubbles
  username="octocat"
  monthFont="'JetBrains Mono', monospace"
  totalFont="Inter, sans-serif"
  totalSize={13}
  totalAlign="center"
  formatTotal={(n) => `${n} commits this year`}
  classNames={{ total: "text-neutral-500" }}
  styles={{ months: { opacity: 0.6 } }}
/>

Custom fonts

The package doesn't ship fonts. It uses whatever your site loads. Load the font as usual, then pass it by name or CSS variable.

import { JetBrains_Mono } from "next/font/google";
const mono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-mono" });

<ContributionBubbles username="octocat" font="var(--font-mono)" />

Dark mode

Pass a CSS variable and set it per theme.

<ContributionBubbles username="octocat" color="var(--dots)" />
:root { --dots: #30a14e; }
.dark { --dots: #39d353; }

Other languages

<ContributionBubbles username="octocat" locale="de" formatTotal={(n) => `${n} Beiträge im letzten Jahr`} />

Your own data

Anything with a date and a count works, not just GitHub.

<ContributionBubbles
  data={[
    { date: "2026-10-01", count: 3 },
    { date: "2026-10-02", count: 7 },
  ]}
/>

Missing days count as zero. Rows with an invalid date or count are skipped.

Private contributions

Private repositories only count if Include private contributions on my profile is on in the user's GitHub profile settings.

Where the data comes from

GitHub doesn't let browsers read the contribution calendar directly, so username uses the free, open source GitHub Contributions API. Results are cached in memory for an hour. If you'd rather not depend on it, load the data your own way and pass it as data.

License

MIT