Skip to content
goodtabs UI
BrowseBrowse components

GitHub button

An outline pill linking to a repo, with the GitHub mark and a formatted star count.

npx shadcn@latest add https://ui.goodtabs.co/r/github-button.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/github-button

The Star label and a count formatted as 98.4k.

Repo name as label

Count only

label null hides the text; no stars hides the count.

Usage

import { GitHubButton } from "@/components/ui/github-button"

<GitHubButton repo="shadcn-ui/ui" stars={98400} />
<GitHubButton repo="vercel/next.js" label="vercel/next.js" stars={136200} size="sm" />

Props

PropTypeDefaultDescription
repostringThe repository as owner/name. Builds the href.
starsnumberStar count, formatted as 1.2k. Leave out to hide it.
labelstring | null"Star"Visible label. Pass the repo name, or null for mark and count only.
size"sm" | "default""default"28px or 32px tall.

Source

github-button/github-button.tsx
import * as React from "react";
import { StarIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

/** 1200 becomes 1.2k, 2400000 becomes 2.4m. Below 1000 the number is shown as is. */
export function formatStars(stars: number): string {
  if (stars < 1000) return String(stars);
  const units: [number, string][] = [
    [1_000_000, "m"],
    [1_000, "k"],
  ];
  const [divisor, suffix] = units.find(([d]) => stars >= d) ?? [1, ""];
  const value = stars / divisor;
  const rounded = value >= 10 ? Math.round(value) : Math.round(value * 10) / 10;
  return `${rounded}${suffix}`;
}

type GitHubButtonProps = Omit<React.ComponentProps<"a">, "href" | "children"> & {
  /** The repository as "owner/name". */
  repo: string;
  /** Star count. Leave out to hide the count. */
  stars?: number;
  /** Visible label. Pass the repo name to show it, or null for the mark and count only. */
  label?: string | null;
  /** 28px or 32px tall. */
  size?: "sm" | "default";
};

function GitHubMark(props: React.ComponentProps<"svg">) {
  return (
    <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" {...props}>
      <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
    </svg>
  );
}

/**
 * An outline pill that links to a GitHub repo, with the mark, a label and a formatted star count.
 * Pass the count yourself: the component never fetches.
 */
export function GitHubButton({
  repo,
  stars,
  label = "Star",
  size = "default",
  className,
  ...props
}: GitHubButtonProps) {
  const hasCount = typeof stars === "number";
  return (
    <Button asChild variant="outline" size={size} className={cn("rounded-full", className)}>
      <a href={`https://github.com/${repo}`} target="_blank" rel="noreferrer" {...props}>
        <GitHubMark data-icon="inline-start" />
        {label && <span>{label}</span>}
        {hasCount && (
          <span className="flex items-center gap-1" aria-hidden="true">
            {label && <span className="mx-0.5 h-3.5 w-px bg-border" />}
            <StarIcon className={size === "sm" ? "size-3" : "size-3.5"} />
            <span className="font-mono text-xs tabular-nums">{formatStars(stars)}</span>
          </span>
        )}
        <span className="sr-only">
          {label ? ` ${repo} on GitHub` : `${repo} on GitHub`}
          {hasCount ? `, ${new Intl.NumberFormat("en-US").format(stars)} stars` : ""}
        </span>
      </a>
    </Button>
  );
}

For AI agents

Hand your agent this page, the markdown at /components/github-button/prompt.md, or the Copy AI prompt button above.

When to use

  • In a header or hero of an open source project, next to the primary action
  • In a footer or docs page where the repo is the canonical link
  • Anywhere a star count is a useful signal and you already have the number

When not to use

  • When you cannot supply the count: it never fetches, and a stale number is worse than none
  • For a generic external link, use a plain outline Button with an anchor
  • For social profile links, use social-links

Agent notes

  • Pass stars from your own fetch (the GitHub API's stargazers_count); formatStars is exported if you need the same format elsewhere
  • It is shadcn's Button with asChild around an anchor, opening in a new tab with rel noreferrer
  • The accessible name is the label plus 'owner/name on GitHub, 1,234 stars', so icon-only use still reads well
  • The GitHub mark is an inline SVG in currentColor, since lucide ships no brand icons