# github-buttons, one widget delivered three different ways

> github-buttons is an unofficial JavaScript library that renders the familiar GitHub follow, star, watch and fork buttons, and it hands the same widget to you as a hosted snippet, a framework component or an importable module. The options are shared across all three, and two of them, the count and the colour scheme, have behaviour worth reading before you ship them.

**buttons/github-buttons** — :octocat: Unofficial github:buttons

- Repository: https://github.com/buttons/github-buttons
- Website: https://buttons.github.io
- Stars: 1,150 · Forks: 251
- Language: JavaScript
- License: BSD-2-Clause
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/buttons-github-buttons

## The same button, three ways to take it

github-buttons is a JavaScript library for rendering the GitHub counters you have seen on a thousand README files: follow, star, watch, fork, and the count next to them. The audience is frontend developers who want those counters to look like GitHub's own, and the interesting design decision is that the library offers three delivery mechanisms for one widget.

The first is the snippet. The README points you at the github:button configurator hosted at buttons.github.io, which is the quickest route and the one most sites use, since a snippet is just markup you paste into a page. The second is the component, with official-looking wrappers in separate repositories: vue-github-button for Vue and react-github-btn for React. The third is the module, where you import the render function and build the element yourself.

The module form is small enough to show in full, and the two overloads explain the design. One takes an options object, the other takes an existing anchor element:

```javascript
import { render } from 'github-buttons'

// export function render(options: object, callback: (el: HTMLElement) => void): void;
render(options, function (el) {
  document.body.appendChild(el) 
})

// export function render(anchor: HTMLAnchorElement, callback: (el: HTMLElement) => void): void;
render(anchor, function (el) {
  anchor.parentNode.replaceChild(el, anchor)
})
```

The callback style rather than a returned element is a deliberate choice for a widget that has to insert itself into a live page, and the anchor overload exists so you can upgrade an existing link in place instead of appending a new node.

## One options contract, three spellings

The options are documented once and then mapped onto each usage mode: for a snippet an option is an attribute on the `<a>` element, for a component it is a prop, and for the module it is a property on the options object. That symmetry is the part that makes the library usable across all three modes without a mental switch.

The options themselves are unremarkable but complete. `href` is a string defaulting to `#` and assigns the GitHub link for the button. `title` is a string with no default and sets the title attribute. `data-icon` is a string defaulting to `octicon-mark-github`, and only a subset of Octicons is bundled, with the subset chosen in rollup.config.mjs, which is worth knowing if you plan to use an icon that is not in the bundle. `data-size` accepts `undefined` or `'large'`. `data-text` sets the button text, and when the button is generated from an anchor and `data-text` is undefined, the text becomes the anchor's own textContent. `aria-label` sets the accessible label, although the README spells the attribute name as `aira-label` in that section, which is a documentation slip rather than a separate option.

Every option has a stated type and a stated default, which is more than most button libraries give you, and it is what makes the three-mode contract hold together.

## data-show-count reads the button type out of the href

The count is opt-in through `data-show-count`, a boolean defaulting to false, and once enabled the type of button is detected from the `href` rather than configured separately. The README lists the patterns: a bare user URL gives a follow button, a user and repository gives a star button, a subscription path gives a watch button, a fork path gives a fork button, and the issues paths give an issues button.

What makes this usable is that the detection is tolerant of the parts of a URL that people actually change. A trailing slash, a query string and a hash fragment do not change the detected type, so `https://github.com/:user/` still reads as follow, `https://github.com/:user?tab=repositories` still reads as follow, and both `https://github.com/:user/:repo#readme` and `https://github.com/:user/:repo/#readme` read as star.

That tolerance also defines the failure mode. The type is inferred, so a URL that looks like a user page but is meant to be something else, or a repository URL with an extra path segment, produces a counter of the wrong kind rather than an error. The same inference is what makes the button configurable at all: you get the right counter without a separate type field, and you lose the ability to override it. The README does not document a way to force a type when the inference is wrong, which is the gap to be aware of.

## data-color-scheme is opt-in, and its history is in the version numbers

The colour scheme option has a default of undefined, and that default is not neutral. It is opt-in, and the documentation is explicit that this has been the case since version 2.3.0. If `data-color-scheme` is undefined the button behaves as it did before 2.3.0, which means the light colour scheme is used under all conditions.

Once the attribute is set to a string, the button inherits a default mapping written in a css-like syntax: `no-preference: light; light: light; dark: dark;`. The first clause covers a system with no stated preference, the second a system that prefers light, the third one that prefers dark. Your own declarations override that default, and the examples show the range: an empty string enables the default mapping, `no-preference: dark;` uses dark when there is no preference, and `dark: light;` forces light everywhere. A full override such as `no-preference: dark; light: dark;` forces dark everywhere.

Since version 2.11.0 there is a shorter form. A single word can be used to force the scheme everywhere, so `data-color-scheme="light"` and `data-color-scheme="dark"` both work, which removes the need to spell out both clauses. The practical advice for anyone integrating this is to set the attribute explicitly rather than relying on the default, because the default is the pre-2.3.0 behaviour and a user on a dark-themed site will get a light button.

## The npm package ships four entry points and a rollup build

The package on npm is named github-buttons, and the version in the manifest is 2.33.1. The distribution layout is the interesting part, because the same source is published four ways. The `main` field points at dist/buttons.common.js for CommonJS, `module` at dist/buttons.esm.js for bundlers, `unpkg` and `jsdelivr` at dist/buttons.min.js, and `types` at dist/buttons.d.ts, so a TypeScript consumer gets declarations without a separate @types package.

The build is rollup, driven by the config file rollup.config.mjs, and the npm scripts are small enough to read in one glance. The build script is `rollup -c rollup.config.mjs` and the lint script is `eslint .`, with the eslint configuration in .eslintrc.json and an .eslintignore alongside it.

```bash
npm run build
npm run lint
```

The test setup is more elaborate than the build, and it explains the tooling in the repository. The test script runs Babel's node wrapper with the preset-env preset against karma, starting karma in single-run mode, and a pretest hook runs the linter first so lint failures block tests. karma.conf.js sits at the root, the test/ directory holds the tests, and the dev dependencies list karma with the Chrome and Firefox launchers, a mocha reporter, a chai adapter, and coverage reporting, along with the octicons and primer primitives packages that supply the icon set and the design tokens. The primitives package is the reason the button can look native rather than merely similar.

## What the documentation does not tell you about the counter

The largest gap in the documentation is the count itself. `data-show-count` is described as showing a dynamic count based on the button type, with the detection rules spelled out in detail, but the README does not say where that number comes from, whether it is fetched by the client at render time, whether it is cached, or what happens when the request fails. For a counter that updates as a repository gains stars, the transport is the part that determines whether the feature is usable for you.

There is a related question about the snippet route, where the same documentation gap exists but the risk is different. If you use the hosted configurator, the count and the button markup are produced outside your own page, and the README does not say what that service guarantees about uptime or behaviour.

The other constraint is the project's own status. It describes itself as an unofficial github:buttons, the repository carries no tagged releases while the npm version sits at 2.33.1, and the licence is BSD-2-Clause, which is permissive and easy to live with, though the naming carries the usual risk of any project that reuses a trademark in its name. For a badge on a personal README, none of that matters. For a product page, the questions to ask are where the count comes from and who is responsible when it stops working, and neither has an answer in this repository.

## Component wrappers and the hosted alternative

Three approaches exist for the same button, and they differ in what they ask of you. The component route takes the decision away: vue-github-button and react-github-btn wrap the library for a framework, so you declare a prop and the framework handles the lifecycle. The module route hands you the render function and the options object, which is the most flexible and the most work. The snippet route asks for nothing but a paste, at the cost of depending on the hosted configurator.

The README also points, under a see also heading, to mdo/github-buttons at ghbtns.com. That is the other implementation of the same idea, and the difference in approach is visible in what each one asks of you: this project publishes a library that you can install, bundle and self-host, with the option of a hosted snippet alongside it, while the other is reached as a hosted service. If your requirement is that no third-party call sits in your page, the npm package and its dist bundles are the way to get there, and the counts question then becomes a question you have to answer yourself.

For anyone choosing between the framework wrappers and the raw module, the deciding factor is simple. If your application already has Vue or React in it, the wrapper is less code and fewer edge cases. If it does not, adding a framework to display one counter is a bad trade, and the module's two-function surface is the cheaper option.

## Conclusion

github-buttons fits a site that wants GitHub counters that look native to GitHub without hand-drawing them, and it fits teams already in npm, since the package ships common, ESM, minified and type entry points. It does not fit a project that needs a documented guarantee about how counts are fetched, because the README describes what data-show-count shows but not how the number arrives, and it does not fit a brand that needs an official endorsement, since the project describes itself as unofficial. Before adopting it, check the data-color-scheme behaviour, which only activates when the attribute is set and otherwise falls back to a light scheme, and confirm that the counter you rely on is not a hard dependency on a service you do not control. The npm version is 2.33.1, the licence is BSD-2-Clause, and the last push was on 2026-10-19.

## FAQ

### How do I install the github-buttons library?

The package is published on npm under the name github-buttons, with the manifest version at 2.33.1. It exposes a CommonJS entry point, an ES module entry point, a minified bundle for unpkg and jsdelivr, and TypeScript declarations, all built with rollup from rollup.config.mjs.

### How do I add a star or follower count to a github-buttons button?

Set the `data-show-count` option to true, which defaults to false. The button type is then detected from the href, so a user and repository URL yields a star button, and a trailing slash, query string or hash does not change the detected type.

### How do I make github-buttons follow the system colour scheme?

Set the `data-color-scheme` attribute. It is opt-in since version 2.3.0, and with no value set the button uses the light scheme under all conditions, as it did before 2.3.0. The default mapping is `no-preference: light; light: light; dark: dark;`, and since version 2.11.0 a single word such as `dark` forces the scheme everywhere.

### Which icons can a github-buttons button use?

The `data-icon` option defaults to `octicon-mark-github` and accepts a string. A subset of the Octicons set is bundled, and the subset is chosen in rollup.config.mjs, so an icon outside that set is not available without changing the build configuration.

### Is github-buttons an official GitHub project?

No. The repository describes itself as an unofficial github:buttons, and the project is published under the BSD-2-Clause licence with a LICENSE file in the repository root.

## Sources

- [buttons/github-buttons on GitHub](https://github.com/buttons/github-buttons)
- [Issues](https://github.com/buttons/github-buttons/issues)
- [License: BSD-2-Clause](https://github.com/buttons/github-buttons/blob/main/LICENSE)
- [Project website](https://buttons.github.io)
- [README](https://github.com/buttons/github-buttons/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/buttons-github-buttons
