Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

Theme

The primitives draw from a set of CSS variables, the theme tokens, in @spawnite/ui’s stylesheet. A game restyles them by setting the variables on :root in its own stylesheet, and every primitive that reads a token follows: the panes, the menus, the loading screen, a health bar, an accent Button. A game sets a token once, not a colour on each element. A game that wants a look without choosing tokens imports one of the shipped themes, and one that wants more than a token can give restyles a primitive’s parts.

Unity’s UI Toolkit and Godot both theme controls through named variables that every control reads, rather than a game restyling each control’s inner parts. The engine matches them, and lists the variables here, because an agent reads this page where a person opens an inspector.

Set a token in the game’s src/styles.css, after the import of ui’s stylesheet:

@import "@spawnite/ui/styles.css";
:root {
--color-menu-accent: var(--color-amber-300);
--color-menu-accent-ink: var(--color-neutral-950);
--font-display: "Bricolage Grotesque", var(--font-sans);
}

Not in @theme: a game’s stylesheet is not a Tailwind root, so an @theme block there reaches the browser unread. Each token is also a Tailwind colour, so bg-menu-accent and text-danger are classes a game’s own elements can wear, and they follow the override too.

The build warns when a game’s stylesheet holds an @theme block. A game adds its own animation as a plain class in @layer components with a top-level @keyframes, as Holdfast’s stylesheet does.

Two themes ship in @spawnite/ui/themes/. Each is a stylesheet that sets every token on :root, so one import after ui’s stylesheet restyles every primitive, and a token set after the import goes past the theme:

Theme Look Import
Glass The default: translucent white panes over the world, white ink, an emerald bar. none, or @import "@spawnite/ui/themes/glass.css";
Slate Opaque dark slabs with a hairline edge, pale ink, a green accent, no blur: Depthfield’s. @import "@spawnite/ui/themes/slate.css";

spawnite create --theme slate writes the import into a new game’s src/styles.css, and the MCP’s new_game takes the same theme. In Storybook, the Theme toolbar switches every story between the two. Godot ships one default theme and a game loads its own .tres; Unity’s UI Toolkit ships a default and a runtime theme file per project. The engine ships two, the glass over the world and the slab, because they differ in kind where a third would differ in colour, and a game’s colours are its own tokens. Holdfast imports glass and sets every token after it in its src/ember.css, which spawnite/examples holds, the example of a theme carried into a game’s own look.

Each token, what reads it, and its default:

Token Read by Default
--color-pane-surface The glass of a Panel, a glass Button, a Tap and a Modal’s pane rgb(255 255 255 / 12%)
--color-pane-sheen The top of the glass’s sheen, a gradient down the pane rgb(255 255 255 / 25%)
--color-pane-sheen-end The bottom of the sheen rgb(255 255 255 / 5%)
--color-pane-edge The glass’s edge, its inset highlight and its border rgb(255 255 255 / 90%)
--color-pane-ink Text on the glass, and the focus ring of a glass control white
--color-pane-shadow The shadow under a pane rgb(0 0 0 / 25%)
--radius-pane The corner of a Panel and a Modal’s pane, the rounded-pane class 1.25rem
--radius-control The corner of a Button, the rounded-control class 1.25rem
--blur-pane How far the world blurs behind the glass, the backdrop-blur-pane class; 0px for a slab 16px
--color-menu-surface The pane of the Escape menu, the performance overlay, a pane Button and menuPane rgb(12 14 20 / 88%)
--color-menu-raised A control at rest in a menu rgb(255 255 255 / 7%)
--color-menu-edge The hairline edge of a menu pane and a pane Button rgb(255 255 255 / 14%)
--color-menu-muted Secondary text in a menu, the muted microphone rgb(255 255 255 / 58%)
--color-menu-accent What is on, chosen or focused in a menu; the fill of an accent Button; a menu slider’s track white
--color-menu-accent-ink Text on the accent neutral-950
--color-menu-ink The text a NumberField, a TextField or a Select holds white
--radius-field The corner of a NumberField, a TextField or a Select 0.375rem
--text-field The size of the text a NumberField, a TextField or a Select holds 15px
--color-health-fill A Bar’s fill, unless the Bar names a color emerald-400
--color-health-track A Bar’s track emerald-950
--color-health-loss The piece a blow just took, flashed before it drains white
--color-danger A readout past a limit, palette.dangerText red-400
--color-loading-background The loading screen’s field, a LoadingRing’s, the Made with notice’s neutral-950
--color-loading-label The loading screen’s muted words neutral-400
--color-loading-indicator The loading screen’s marks neutral-200
--color-loading-bar The loading bar’s fill neutral-200
--color-loading-track The loading bar’s track rgb(255 255 255 / 14%)
--color-loading-text The loading screen’s text neutral-100
--color-stick-ring The Joystick’s ring rgb(255 255 255 / 70%)
--color-stick-knob The Joystick’s knob rgb(255 255 255 / 90%)
--color-reading-good A performance reading within budget emerald-400
--color-reading-warn A reading near the budget amber-400
--color-reading-bad A reading past it red-400
--color-voice-speaking The microphone’s level while the room hears the player emerald-400
--color-voice-muted The slash across a muted microphone red-500
--font-display Titles and big numbers: a Heading, a LevelBanner, the font-display class var(--font-sans)

A Tailwind name in the Default column, such as emerald-400, is that colour from Tailwind’s palette, var(--color-emerald-400).

A game’s own elements take a token through the class that names it, border-pane-edge or text-menu-accent, rather than a palette class such as text-amber-200, so one change in styles.css restyles the HUD and the menus together.

A token restyles every primitive at once. For one primitive that should look different from the rest, each primitive with more than one visible part takes classNames, a map from a part’s name to the classes it wears, beside the className that goes on its root. Each part’s classes merge over the primitive’s own, so classNames={{ value: "text-amber-300" }} recolours a Counter’s number and leaves its icon. Mantine and CharacterUI name their parts the same way, and an agent that has styled either knows the shape.

Primitive Parts
Button root, icon
Counter root, icon, value
Bar root, track, fill, reserved, value
Modal dialog, pane, title, overline, actions
import { Counter } from "@spawnite/engine";
export function Streak({ streak }: { streak: number }) {
return (
<Counter
icon="flame"
label="Streak"
value={streak}
classNames={{ icon: "text-menu-accent", value: "font-display" }}
/>
);
}

A Panel is one surface, so it takes className alone, and a Joystick and a Tap take the engine’s tokens and a className on the control.