Design the theme
The theme is /theme.json: the game's colors, light and dark modes, radii, shadows, spacing and page background. The rules pages and the marketing page use it through $ tokens. The client reads it with useGameTheme. Follow these steps in order.
1. Choose the colors
Write every color the game uses in colors, as palettes of shades:
"colors": {
"neutral": { "50": "#f4f4f2", "800": "#232323" },
"accent": { "500": "#4d6a7c" }
}
Start each palette and token name with a letter or _, then use letters, digits and _. A hyphen is an error. Name shades with letters, digits and _.
2. Name each color by its job
semanticTokens names what each color is for, with a value for light mode and one for dark mode. A value is a color, or a reference to a shade written palette.shade without $:
"semanticTokens": {
"text": { "primary": { "light": "neutral.800", "dark": "neutral.50" } },
"bg": { "surface": { "light": "#ffffff", "dark": "#1b1b1f" } }
}
On the pages, only semantic tokens switch between light and dark. Every other token is fixed. Give every text, background and border color on the pages a semantic token with both modes.
3. Write the whole file
Write /theme.json with write_file. Boardweaver reads the theme only at that path. All fields:
| Field | Required | Holds |
|---|---|---|
version |
yes | 1 |
fonts |
yes | { "heading", "body", "mono" }, each sans, serif or mono |
colors |
yes | palettes of shades |
semanticTokens |
yes | groups of { light, dark } tokens |
radii |
yes | named lengths, such as { "md": 8 } |
shadows |
yes | named shadows: "none" or up to 4 { offsetX, offsetY, blur?, spread?, color, inset? } |
palettes |
yes | more named color sets, referenced as palettes.name.key |
spacing |
yes | named lengths, such as { "4": 16 } |
background |
no | { light, dark }, both required: each { baseColor, layers, border? }, with at most 8 layers |
defaultColorMode |
no | light or dark. Without it, the pages follow the visitor's setting. |
mood, voice |
no | short words, at most 64 characters, that describe the game's style |
A background layer is solid, linear-gradient, radial-gradient, pattern (grid, scanlines, dots, diagonal-lines) or blur-orb. A page without background is white, so with dark semantic tokens also set background.dark.
write_file and patch_file refuse a file that is not JSON (INVALID_THEME_JSON) or does not fit (INVALID_THEME), and say which field is wrong.
4. Use it on the pages
In a page style, a theme value is a $ token:
| Token | Reads |
|---|---|
$semanticTokens.text.primary |
a semantic token, which follows light and dark |
$colors.accent.500 |
a shade |
$palettes.name.key |
a palette entry |
$radii.md, $shadows.card, $spacing.4 |
a named value |
Boardweaver drops a token that does not exist, without an error, so check each token name against /theme.json. Set fontFamily to sans, serif or mono directly.
Run validate_code until it answers no page errors.
5. Use it in the client
useGameTheme() gives the theme as written, with references not resolved. useColorMode() gives "light" or "dark", and changes when the player changes their setting. Resolve a semantic token in the client:
import { useColorMode, useGameTheme, type GameTheme } from "boardweaver/react";
function resolveColor(theme: GameTheme, value: string): string {
const [palette, shade] = value.split(".");
return theme.colors[palette]?.[shade] ?? value;
}
export function useToken(group: string, token: string): string | undefined {
const theme = useGameTheme();
const mode = useColorMode();
const pair = theme.semanticTokens[group]?.[token];
return pair && resolveColor(theme, pair[mode]);
}
Boardweaver paints no background behind the board, so paint it in the client. Use the theme's colors in both modes.
6. Commit
Call commit.