Adding a New Theme to Turbo-Themes
Turbo-themes uses a token-based theming system: theme definitions in
src/themes/packs/ define color tokens, src/themes/registry.ts collects them, the
build generates CSS from tokens, the theme-selector package provides UI components,
and the site plus example projects consume the themes.
Related Skills
stand-ts: TypeScript coding standards
commit: semantic commit format when committing changes
turbo-verify: use after implementation to verify completeness
How to Implement: Copy a Reference, Discover the Touchpoints
Do NOT write theme packs from a template or trust a memorized file list. Instead:
Pick a reference theme and mirror its files:
- Synced theme with npm package:
src/themes/packs/catppuccin.synced.ts +
scripts/sync-catppuccin.mjs
- Synced theme:
src/themes/packs/rose-pine.synced.ts
- Manual theme with license/source metadata:
src/themes/packs/nord.ts
- Manual theme:
src/themes/packs/bulma.ts, src/themes/packs/dracula.ts
Discover every touchpoint by grepping for an existing theme's variant id
(pick one from src/themes/registry.ts, e.g. a rose-pine or catppuccin variant):
# Every file that mentions an existing variant — the new theme needs the
# same touchpoints (packs, token JSON, icons, site data, examples, tests)
rg -l 'rose-pine-moon' --hidden -g '!node_modules' -g '!dist'
# Family-level touchpoints (type unions, family maps, vendor metadata)
rg -l 'rose-pine' src/ packages/ apps/ scripts/
Also check root-level registrations the greps can miss because they do not
mention variant ids: the theme:sync script wiring in package.json and
size limits in test/integration/bundle-size.test.ts.
Verify with the current repo, not this skill: if a file in the grep output is
generated (check for a "generated" header or a build/theme:sync script that
writes it), update the source and rebuild instead of hand-editing.
Files to Create
scripts/sync-<theme>.mjs # Optional: sync from npm package
src/themes/packs/<theme>.synced.ts # Theme definitions (or <theme>.ts for manual)
schema/tokens/themes/<theme-id>.tokens.json # W3C Design Token file, one per variant
assets/img/<theme-id>.png # Theme icon, one per variant
- Theme pack: copy the structure of the reference pack —
id, name,
homepage, license (spdx/url/copyright), source (package/version/repository
for synced themes), and flavors with complete token groups (background,
text, brand, state, border, accent, typography, content). Do NOT
add iconUrl to flavors — icons resolve via VENDOR_ICON_MAP in theme-mapper.ts.
- W3C token files: mirror an existing file in
schema/tokens/themes/ —
$value/$type format, $schema pointing to
../../turbo-themes.schema.json#/$defs/ThemeFile.
- Icons: PNG per variant (typically 24x24), visually distinct for light/dark.
Sync Script Best Practices (if the theme has an npm palette package)
Copy scripts/sync-catppuccin.mjs and adapt. Key rules:
- Output path must be
src/themes/packs/ — the registry imports from there,
NOT packages/core/src/themes/packs/.
- Read the version from
node_modules/<pkg>/package.json and populate
source.version for traceability.
- Normalize hex colors — source packages include
# inconsistently.
- Deterministic ordering — sort variant keys for reproducible builds.
- Add the script to
theme:sync in package.json — the build pipeline must
generate the file before TypeScript compilation.
Files to Update
Enumerate with the discovery greps above; the recurring touchpoints are:
src/themes/registry.ts — import the pack and spread its flavors into
allFlavors
packages/theme-selector/src/types.ts — add to the ThemeFamily type union
packages/theme-selector/src/constants.ts — add to THEME_FAMILIES
(name + description)
packages/theme-selector/src/theme-mapper.ts — add to VENDOR_FAMILY_MAP,
VENDOR_ICON_MAP (string, or {light, dark} AppearanceIcons object when the
family has both appearances), and FLAVOR_DESCRIPTIONS per variant
apps/site/src/data/theme-meta.ts — single source of truth for the site:
add to themeGroups, themeNames (short dropdown labels), and themeIcons.
validThemeIds is derived automatically; BaseLayout.astro and
ThemeDropdown.astro are data-driven from this file — no direct edits there.
apps/site/src/pages/themes.astro — sidebar family section + JS themeNames
apps/site/src/pages/index.astro — hero preview strip buttons
scripts/prepare-style-dictionary.mjs — add to vendorMeta (name + homepage)
test/integration/bundle-size.test.ts — increase budget only if needed
package.json — append sync script to theme:sync (if using one)
Example projects
Example files hardcode theme lists; discover the current set instead of assuming:
# Web examples with hardcoded theme arrays / dropdowns
rg -l 'VALID_THEMES|LIGHT_THEMES|lightThemes' examples/
rg -l 'THEMES' examples/stackblitz/react examples/stackblitz/vue
# Swift example touchpoints
rg -l 'ThemeId|ThemeDefinition' examples/swift-swiftui/
In each hit, add the new variants everywhere an existing variant appears:
<select> options, VALID_THEMES/LIGHT_THEMES/THEMES arrays, Swift
ThemeId.swift enum cases, ThemeRegistry.swift ThemeDefinition palettes, and
ThemeRegistryTests.swift counts/labels. Files that import from
@lgtm-hq/turbo-themes-core/tokens (React hooks, Vue composables, Bootstrap
main.ts) auto-update — skip any file where the grep hit is an import, not a
hardcoded list.
Naming Conventions
- Theme ID: lowercase with hyphens (e.g.,
rose-pine-moon)
- Variant label (token
label field): full display name including family
(e.g., "Gruvbox Dark Hard") — match existing .tokens.json files
- Short label (
themeNames in theme-meta.ts): condensed dropdown label
(e.g., "Dark Hard", "Moon")
- Vendor / family: the theme family identifier (e.g.,
rose-pine)
Build and Test
uv run lintro chk # Lint
bun run build # Core build
bun run examples:build # Example projects
bun run test # Unit tests
bun run examples:test # Example E2E tests
cd apps/site && bun run build # Site build
cd apps/site && bun run dev # Visual check
Tip: the turbo-test skill runs the full pipeline automatically.
Common Gotchas
- Theme reverts on navigation / wrong label / missing icon: variants missing
from
themeGroups/themeNames/themeIcons in
apps/site/src/data/theme-meta.ts (the site is data-driven from this file —
do not edit BaseLayout.astro for these)
- Theme not in dropdown / wrong group:
VENDOR_FAMILY_MAP missing or wrong
- Tests fail on theme order: use
data-theme-id attribute lookups, not
array indices
- Bundle size test fails: increase the budget in bundle-size.test.ts
- CI
Cannot find module './packs/<theme>.synced.js': sync script missing
from theme:sync in package.json
- tokens.json shows wrong name/homepage: missing from
vendorMeta in
prepare-style-dictionary.mjs
- Generated assets outdated: run
bun run build and commit the generated
files (theme-selector bundles, tokens.json in core/python/swift)
- Type changes: if adding interfaces, update BOTH
src/themes/types.ts AND
packages/core/src/themes/types.ts — separate files kept in sync
- Visual regression fails after hero changes: snapshots are generated on
Linux CI — run the
maintenance-generate-snapshots.yml workflow
- Missing from examples: re-run the example discovery greps and diff the new
theme's hits against an existing theme's hits
1---2name: turbo-add3description: Guide for adding a new theme family to turbo-themes. Use when implementing Nord, Solarized, Gruvbox, Tokyo Night, One Dark, Ayu, Kanagawa, Everforest, Radix, or any new theme.4---56# Adding a New Theme to Turbo-Themes78Turbo-themes uses a token-based theming system: theme definitions in9`src/themes/packs/` define color tokens, `src/themes/registry.ts` collects them, the10build generates CSS from tokens, the theme-selector package provides UI components,11and the site plus example projects consume the themes.1213## Related Skills1415- `stand-ts`: TypeScript coding standards16- `commit`: semantic commit format when committing changes17- `turbo-verify`: use after implementation to verify completeness1819## How to Implement: Copy a Reference, Discover the Touchpoints2021Do NOT write theme packs from a template or trust a memorized file list. Instead:22231. **Pick a reference theme** and mirror its files:24 - Synced theme with npm package: `src/themes/packs/catppuccin.synced.ts` +25 `scripts/sync-catppuccin.mjs`26 - Synced theme: `src/themes/packs/rose-pine.synced.ts`27 - Manual theme with license/source metadata: `src/themes/packs/nord.ts`28 - Manual theme: `src/themes/packs/bulma.ts`, `src/themes/packs/dracula.ts`292. **Discover every touchpoint** by grepping for an existing theme's variant id30 (pick one from `src/themes/registry.ts`, e.g. a rose-pine or catppuccin variant):3132 ```bash33 # Every file that mentions an existing variant — the new theme needs the34 # same touchpoints (packs, token JSON, icons, site data, examples, tests)35 rg -l 'rose-pine-moon' --hidden -g '!node_modules' -g '!dist'3637 # Family-level touchpoints (type unions, family maps, vendor metadata)38 rg -l 'rose-pine' src/ packages/ apps/ scripts/39 ```40413. Also check root-level registrations the greps can miss because they do not42 mention variant ids: the `theme:sync` script wiring in `package.json` and43 size limits in `test/integration/bundle-size.test.ts`.44454. Verify with the current repo, not this skill: if a file in the grep output is46 generated (check for a "generated" header or a `build`/`theme:sync` script that47 writes it), update the source and rebuild instead of hand-editing.4849## Files to Create5051```text52scripts/sync-<theme>.mjs # Optional: sync from npm package53src/themes/packs/<theme>.synced.ts # Theme definitions (or <theme>.ts for manual)54schema/tokens/themes/<theme-id>.tokens.json # W3C Design Token file, one per variant55assets/img/<theme-id>.png # Theme icon, one per variant56```5758- **Theme pack**: copy the structure of the reference pack — `id`, `name`,59 `homepage`, `license` (spdx/url/copyright), `source` (package/version/repository60 for synced themes), and `flavors` with complete token groups (`background`,61 `text`, `brand`, `state`, `border`, `accent`, `typography`, `content`). Do NOT62 add `iconUrl` to flavors — icons resolve via `VENDOR_ICON_MAP` in theme-mapper.ts.63- **W3C token files**: mirror an existing file in `schema/tokens/themes/` —64 `$value`/`$type` format, `$schema` pointing to65 `../../turbo-themes.schema.json#/$defs/ThemeFile`.66- **Icons**: PNG per variant (typically 24x24), visually distinct for light/dark.6768### Sync Script Best Practices (if the theme has an npm palette package)6970Copy `scripts/sync-catppuccin.mjs` and adapt. Key rules:71721. **Output path must be `src/themes/packs/`** — the registry imports from there,73 NOT `packages/core/src/themes/packs/`.742. **Read the version from `node_modules/<pkg>/package.json`** and populate75 `source.version` for traceability.763. **Normalize hex colors** — source packages include `#` inconsistently.774. **Deterministic ordering** — sort variant keys for reproducible builds.785. **Add the script to `theme:sync` in package.json** — the build pipeline must79 generate the file before TypeScript compilation.8081## Files to Update8283Enumerate with the discovery greps above; the recurring touchpoints are:8485- `src/themes/registry.ts` — import the pack and spread its flavors into86 `allFlavors`87- `packages/theme-selector/src/types.ts` — add to the `ThemeFamily` type union88- `packages/theme-selector/src/constants.ts` — add to `THEME_FAMILIES`89 (name + description)90- `packages/theme-selector/src/theme-mapper.ts` — add to `VENDOR_FAMILY_MAP`,91 `VENDOR_ICON_MAP` (string, or `{light, dark}` AppearanceIcons object when the92 family has both appearances), and `FLAVOR_DESCRIPTIONS` per variant93- `apps/site/src/data/theme-meta.ts` — **single source of truth for the site**:94 add to `themeGroups`, `themeNames` (short dropdown labels), and `themeIcons`.95 `validThemeIds` is derived automatically; `BaseLayout.astro` and96 `ThemeDropdown.astro` are data-driven from this file — no direct edits there.97- `apps/site/src/pages/themes.astro` — sidebar family section + JS `themeNames`98- `apps/site/src/pages/index.astro` — hero preview strip buttons99- `scripts/prepare-style-dictionary.mjs` — add to `vendorMeta` (name + homepage)100- `test/integration/bundle-size.test.ts` — increase budget only if needed101- `package.json` — append sync script to `theme:sync` (if using one)102103### Example projects104105Example files hardcode theme lists; discover the current set instead of assuming:106107```bash108# Web examples with hardcoded theme arrays / dropdowns109rg -l 'VALID_THEMES|LIGHT_THEMES|lightThemes' examples/110rg -l 'THEMES' examples/stackblitz/react examples/stackblitz/vue111112# Swift example touchpoints113rg -l 'ThemeId|ThemeDefinition' examples/swift-swiftui/114```115116In each hit, add the new variants everywhere an existing variant appears:117`<select>` options, `VALID_THEMES`/`LIGHT_THEMES`/`THEMES` arrays, Swift118`ThemeId.swift` enum cases, `ThemeRegistry.swift` `ThemeDefinition` palettes, and119`ThemeRegistryTests.swift` counts/labels. Files that import from120`@lgtm-hq/turbo-themes-core/tokens` (React hooks, Vue composables, Bootstrap121main.ts) auto-update — skip any file where the grep hit is an import, not a122hardcoded list.123124## Naming Conventions125126- **Theme ID**: lowercase with hyphens (e.g., `rose-pine-moon`)127- **Variant label** (token `label` field): full display name including family128 (e.g., "Gruvbox Dark Hard") — match existing `.tokens.json` files129- **Short label** (`themeNames` in theme-meta.ts): condensed dropdown label130 (e.g., "Dark Hard", "Moon")131- **Vendor / family**: the theme family identifier (e.g., `rose-pine`)132133## Build and Test134135```bash136uv run lintro chk # Lint137bun run build # Core build138bun run examples:build # Example projects139bun run test # Unit tests140bun run examples:test # Example E2E tests141cd apps/site && bun run build # Site build142cd apps/site && bun run dev # Visual check143```144145**Tip:** the `turbo-test` skill runs the full pipeline automatically.146147## Common Gotchas1481491. **Theme reverts on navigation / wrong label / missing icon**: variants missing150 from `themeGroups`/`themeNames`/`themeIcons` in151 `apps/site/src/data/theme-meta.ts` (the site is data-driven from this file —152 do not edit BaseLayout.astro for these)1532. **Theme not in dropdown / wrong group**: `VENDOR_FAMILY_MAP` missing or wrong1543. **Tests fail on theme order**: use `data-theme-id` attribute lookups, not155 array indices1564. **Bundle size test fails**: increase the budget in bundle-size.test.ts1575. **CI `Cannot find module './packs/<theme>.synced.js'`**: sync script missing158 from `theme:sync` in package.json1596. **tokens.json shows wrong name/homepage**: missing from `vendorMeta` in160 prepare-style-dictionary.mjs1617. **Generated assets outdated**: run `bun run build` and commit the generated162 files (theme-selector bundles, tokens.json in core/python/swift)1638. **Type changes**: if adding interfaces, update BOTH `src/themes/types.ts` AND164 `packages/core/src/themes/types.ts` — separate files kept in sync1659. **Visual regression fails after hero changes**: snapshots are generated on166 Linux CI — run the `maintenance-generate-snapshots.yml` workflow16710. **Missing from examples**: re-run the example discovery greps and diff the new168 theme's hits against an existing theme's hits