Installation
Install GameKit UI games with the shadcn CLI — by registry URL or the @gamekitui namespace. Includes the usage pattern and the shared props every game accepts.
Install a game
GameKit UI is listed in the official shadcn registry directory, so install any game by short name — the CLI resolves the @gamekitui namespace automatically, no setup required:
npx shadcn@latest add @gamekitui/snakeReplace snake with any game (tic-tac-toe, 2048, flappy, …). The CLI writes the component into your project and rewrites the cn import to your own @/lib/utils — the only assumption is the standard shadcn helper.
You can also install by direct registry URL if you prefer:
npx shadcn@latest add https://gamekitui.com/r/snake.jsonPin the registry (optional)
@gamekitui installs work straight from the directory, but you can pin the registry in your project config:
npx shadcn@latest registry add @gamekituiOr add it to components.json by hand:
{
"registries": {
"@gamekitui": "https://gamekitui.com/r/{name}.json"
}
}Use it
// app/not-found.tsx
import { 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>
);
}Bundle size & lazy-loading
The installed .tsx looks large (~15–28 KB) because each game inlines its own engine and theme hooks to stay a true single-file drop-in — but that's the source, not what ships. Built for production, every game is 2–4 KB minified + gzipped.
Each game is a self-contained module, so it's trivially code-split. Lazy-load it and it never touches your initial bundle — it only downloads when the easter egg actually renders:
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" />,
});Shared props
Every game accepts a common subset of props:
| Prop | Type | Description |
|---|---|---|
className | string | Tailwind classes on the wrapper. |
width / height | number | Logical size in CSS px (canvas games scale via DPR). |
paused | boolean | Externally pause the game. |
autoFocus | boolean | Focus on mount. Defaults to true. |
captureGlobalKeys | boolean | Listen for keys on window so the game works without being focused first. Defaults to true — set false when several games share a page, or it sits in scrollable content, so it only responds while focused. |
persistHighScore | boolean | string | localStorage key, or a default per game. |
onScoreChange | (score: number) => void | Fires when the score changes. |
onGameOver | (r: { score: number; won: boolean }) => void | Fires on game over. |