GameKit UI

Drop-in browser games for shadcn.
Minimal, themeable React/TSX games installable with the shadcn CLI — perfect for 404 pages, empty states, loading screens, and landing-page easter eggs.

Games
Dependencies
Size

· · · ·


bash
npx shadcn@latest add @gamekitui/snake

No provider. No peer deps. No init step. Each game is a single drop-in file that inherits your shadcn theme automatically — light/dark and any preset. Drop one on a 404 and it plays on the first keypress.

✨ Highlights

  • 🪶 Minimal — each game is a single file with zero npm dependencies beyond react and your existing cn helper. 2–4 KB minified + gzipped (the raw .tsx is bigger because it inlines its engine and theme hooks), and lazy-loadable so it never weighs down your initial bundle.
  • 🎨 Themeable — your --primary / --secondary / --accent drive the playfield, not just the chrome. Canvas games read the tokens at runtime; DOM games use Tailwind token classes. Change your theme and the games recolor live.
  • 🚫 Zero assets — no images, audio, or fonts. Every pixel is drawn with CSS or <canvas>.
  • ♿ Accessible — keyboard + touch input, visible focus rings, aria-live announcements, and prefers-reduced-motion support in every game.

🕹️ The lineup

GameSurfaceInstall
🐍 Snakecanvasnpx shadcn@latest add @gamekitui/snake
⭕ Tic-Tac-ToeDOMnpx shadcn@latest add @gamekitui/tic-tac-toe
🔢 2048DOMnpx shadcn@latest add @gamekitui/2048
🃏 Memory MatchDOMnpx shadcn@latest add @gamekitui/memory-match
🔨 Whack-a-MoleDOMnpx shadcn@latest add @gamekitui/whack-a-mole
💣 MinesweeperDOMnpx shadcn@latest add @gamekitui/minesweeper
🏓 Pongcanvasnpx shadcn@latest add @gamekitui/pong
🧱 Breakoutcanvasnpx shadcn@latest add @gamekitui/breakout
🦖 Dino Runnercanvasnpx shadcn@latest add @gamekitui/dino-runner
🐦 Flappycanvasnpx shadcn@latest add @gamekitui/flappy

Base registry URL: https://gamekitui.com/r/{name}.json

🚀 Usage

tsx
// app/not-found.tsximport { Snake } from "@/components/games/snake";
export default function NotFound() {  return (    <main className="grid min-h-svh place-items-center">      <div className="space-y-4 text-center">        <h1 className="text-4xl font-semibold">404</h1>        <p className="text-muted-foreground">Play a round while you decide where to go.</p>        <Snake className="mx-auto rounded-lg border" width={320} />      </div>    </main>  );}

That's it — no setup. On a single-game page like this, the game captures keyboard input globally, so it responds the moment a visitor presses a key (no click-to-focus needed).

Bundle size & lazy-loading

The installed .tsx looks big (~15–28 KB) because it inlines its own engine and theme hooks to stay a true single-file drop-in — but that's source, not what ships. Built for production each game is 2–4 KB minified + gzipped.

Because every game is a self-contained module, it's trivially code-split — lazy-load it so it never touches your initial bundle and only downloads when the easter egg actually renders:

tsx
import dynamic from "next/dynamic";
const Snake = dynamic(() => import("@/components/games/snake").then((m) => m.Snake), {  ssr: false,  loading: () => <div className="aspect-square w-full animate-pulse rounded-lg bg-muted" />,});

Pin the registry (optional)

@gamekitui/<game> installs resolve straight from the — no setup needed. If you'd rather pin the registry in your project config:

bash
npx shadcn@latest registry add @gamekitui

Or add "@gamekitui": "https://gamekitui.com/r/{name}.json" to the registries field of your components.json by hand. You can also install any game by its direct URL: npx shadcn@latest add https://gamekitui.com/r/snake.json.

⚙️ Shared props

Every game accepts a common subset of props:

PropTypeDescription
classNamestringTailwind classes on the wrapper.
width / heightnumberLogical size in CSS px (canvas games scale via DPR).
pausedbooleanExternally pause the game.
autoFocusbooleanFocus on mount. Defaults to true.
captureGlobalKeysbooleanListen for keys on window so the game works without being focused first. Defaults to true — set false when several games share a page, or the game sits in scrollable content, so it only responds while focused.
persistHighScoreboolean | stringlocalStorage key, or a default per game.
onScoreChange(score: number) => voidFires when the score changes.
onGameOver(r: { score: number; won: boolean }) => voidFires on game over.

🗂️ Repository layout

apps/web                  Next.js 16 marketing + docs site; serves /r/*.jsonpackages/registry         The game sources (one self-contained file each)packages/game-core        Shared contract types + reference hooks (zero runtime)packages/ui               shadcn primitives used by the sitescripts/build-registry.ts Emits apps/web/public/r/*.json from the game sources

🛠️ Development

bash
bun installbun run dev            # builds the registry, then starts the site on :3001

Other scripts:

bash
bun run build          # registry build + next buildbun run check-types    # typecheck every workspacecd packages/registry && bun smoke.tsx   # render smoke test for every game

🤝 Contributing

Contributions are welcome! See for the "add a new game" guide — the engine/wrapper template, the shared props contract, the theme-token mapping, and the size budget. Please also review our .

Found a bug or have an idea? .

⚠️ Disclaimer

GameKit UI is an independent, unaffiliated community project. It is not built, sponsored, or endorsed by the shadcn/ui team. It uses the shadcn registry system. The shadcn- prefix is reserved for official projects, which is why this project is named gamekitui (not shadcn-games).

📄 License

© GameKit UI contributors

Built with ❤️ for the shadcn community.