{"doc":"gamestage-ui","title":"Gamestage UI","markdown":"# Gamestage UI\n\nGamestage UI is a component library for the interface a live game wears: the\nchoices, the score, the timer, the rounds, the streaks, the rewards and the\nleaderboard. It renders the game around the action rather than owning the\naction, so the video, the 3D scene or the canvas stays yours.\n\nComponents are copied into your project rather than installed as a dependency.\nYou get the source, you edit it, and nothing upgrades under you.\n\n## Install a component\n\nGamestage UI serves from a private registry, so it needs a token. The token is a\nGitHub token with read access to `gamestageai/gamestage-ui`, and `gh auth token`\nprints one.\n\n```sh\nexport GAMESTAGE_REGISTRY_TOKEN=$(gh auth token)\nnpx gamestage-ui add choice\n```\n\nThat writes the component and its stylesheet into `components/gamestage/`,\nbrings in whatever it depends on, and installs any npm packages it needs with\nthe package manager your lockfile names. A file that is already there is left\nalone unless you pass `--overwrite`, and the command prints every path it wrote.\n\n**If you get \"not found\", check the token before you hunt the component.** A\nrequest without one returns 404 rather than 403, so a missing token looks\nexactly like a missing component.\n\n| Command | What it does |\n| --- | --- |\n| `npx gamestage-ui add <name...>` | Copies components in, with what they depend on |\n| `npx gamestage-ui list` | Prints every component the registry serves |\n| `npx gamestage-ui init` | Writes `gamestage.json`, which pins the registry URL |\n\n`init` is optional. The registry URL is built in, and `gamestage.json` matters\nonly when you want to point at a different one. The token is never written to a\nfile.\n\n## A question on screen\n\n```tsx\nimport { useState } from 'react'\nimport { Choice, type ChoiceResult } from '@/components/gamestage/choice'\n\nexport function Question() {\n  const [result, setResult] = useState<ChoiceResult>()\n\n  return (\n    <Choice\n      label=\"Which driver led the most laps?\"\n      presentation=\"buttons\"\n      commitOnSelect\n      result={result}\n      options={[\n        { id: 'ver', label: 'Verstappen' },\n        { id: 'nor', label: 'Norris' },\n        { id: 'lec', label: 'Leclerc' },\n      ]}\n      onCommitted={(ids) => setResult({ [ids[0]]: ids[0] === 'ver' ? 'correct' : 'wrong' })}\n    />\n  )\n}\n```\n\nThat renders three answers, records the pick and shows the outcome you hand\nback. The same component renders as cards, a list, a wheel or an overlay over a\nlive scene by changing `presentation`, because a quiz grid and a card picker are\none choice wearing different clothes.\n\n## What it will not do\n\nThis is the part that saves time later. Gamestage UI performs none of these and\nwill not start.\n\n| It does not | You do |\n| --- | --- |\n| Sign anyone in | Pass the player you already have |\n| Store progress | Persist it wherever you keep state |\n| Call your API | Call it from the callbacks |\n| Decide who won | Settle the outcome and pass it back |\n| Ask for consent | Render your own consent surface |\n| Send analytics | Forward the callbacks to your own tracking |\n\nThe boundary is enforced rather than promised: no component imports anything but\nReact, and a gate check fails the build if one reaches for `fetch`, storage, a\nsocket or a cookie.\n\n**This is why it sits beside the Engine rather than inside it.** Gamestage's\nEngine decides whether a play was any good and holds the score. Gamestage UI\ndraws what the Engine decided. Neither knows about the other, and you can use\neither on its own.\n\n## Theming\n\nEvery colour, size, duration and typeface is a token. Components read tokens and\nnever define their own, so changing a token changes every surface at once.\n\n```html\n<html data-gs-theme=\"arcade\" data-gs-context=\"light\">\n```\n\nA theme changes the palette, the typefaces, the corner radius, the edge\ntreatment, and what a control does when you press it: travel on an offset base,\nscale, or hold still and shift colour instead. Three themes ship today: arcade,\npaper and stadium.\n\n## Selecting on it from outside\n\nComponents describe themselves with `data-gs-*` attributes, so your code and\nyour tests find things without depending on class names or on the words on\nscreen. A theme changes the first and a translation changes the second.\n\n```css\n[data-gs-part=\"label\"]                       /* every label, everywhere */\n[data-gs-scope=\"stat\"][data-gs-part=\"label\"] /* only a stat's */\n[data-gs-state=\"wrong\"]                      /* a settled wrong answer */\n```\n\nThose attributes are public API. One is documented before it is emitted, and a\ngate check fails the build when the documentation and the components disagree.\n\n## What the registry serves\n\n**Grouped by what it's for.** The groups are the library's own: each item\ndeclares its category, and the Gallery at\n[ui.gamestage.ai](https://ui.gamestage.ai/) reads the same field, so what you\nsee there and what you read here cannot disagree. New items ship often; treat\nthis as a sample rather than a count.\n\n| Group | Items |\n| --- | --- |\n| Controls | `button`, `choice`, `selector`, `slider`, `toggle` |\n| Player | `identity`, `player-card`, `presence`, `profile` |\n| Game state | `hud`, `lives`, `multiplier`, `round`, `score`, `stat`, `streak`, `timer` |\n| Progress | `level-ring`, `milestones`, `progress-bar`, `quest`, `segments` |\n| Rewards | `inventory`, `reward` |\n| Competition | `leaderboard`, `podium`, `versus` |\n| Charts | `chart` (bar, line, area, radial) |\n| Flow and guidance | `coach-mark`, `onboarding`, `tip`, `tutorial-step` |\n| Media | `media-frame`, `story` |\n| Game feel | `feedback` |\n| Composition | `nav`, `stage` |\n| Application surfaces | `code-entry`, `consent`, `discovery`, `empty-state`, `error-state`, `field`, `form`, `login`, `prize-entry`, `recovery-code`, `sponsor` |\n| Foundations | `adapter`, `cues`, `fonts`, `fonts-arcade`, `fonts-paper`, `theme-arcade`, `theme-paper`, `theme-stadium`, `tokens` |\n\n`glyphs` is in the registry, ungrouped.\n\nYou rarely name the last two rows. Application surfaces are whole screens rather\nthan parts of one, and Foundations are the tokens, typefaces, themes and the\nhost adapter that other items pull in for themselves, so asking for `choice`\nbrings what it needs without you listing it.\n\n`npx gamestage-ui list` prints what the registry holds today, which is the list\nto trust over this one.\n\nThree things arrived in the registry on 28 September. `nav` takes\n`presentation=\"glass\"` and `labels=\"hidden\"` for an icon-only bar. `story` takes\n`gestures` (tap, swipe across, swipe down to close) and `fullscreen`. And every\npressable control presses down under a thumb on an iPhone and never selects its\nwords.\n"}