BitaKit
How-to guides

Themes and appearance

Add an app theme, switch color schemes and save personal copies.

A theme supplies shared colors, fonts and surface tokens. Appearance settings let each user choose a theme, navigation layout and other preferences. Shell provides the theme manager; Core saves its values; App Preferences displays its Settings page.

This guide assumes a configured app with @bitakit/shell and @bitakit/app-preferences, including the generated CSS entry and mounted Settings route. Use the matching local packages for features in this checkout. The minimal Counter app does not include Shell automatically.

1. Add your first theme

Create src/themes/ocean.json:

{
  "schemaVersion": 1,
  "id": "ocean",
  "name": "Ocean",
  "light": {
    "primary": "#075985",
    "primary-foreground": "#ffffff"
  },
  "dark": {
    "primary": "#7dd3fc",
    "primary-foreground": "#082f49"
  }
}

These are CSS variable names without --. Unspecified tokens keep the central theme's appearance, so this example only changes the primary color pair.

Run your app's npm run typecheck to regenerate discovery and check the composition, then restart its development server. Open Settings → Appearance (/settings/theme with the default Settings route). Under Themes, select Ocean · App.

The theme is now active. Existing controls that use the primary tokens pick up its colors. You do not register a Settings page, add a provider or edit generated files.

From a theme file to the app

From a theme file to the app. Discovery collects app JSON at build time. At runtime, the theme service reads the selected key from Core settings. Shell projects the active mode tokens onto the document; controls inherit them.
Discovery collects app JSON at build time. At runtime, the theme service reads the selected key from Core settings. Shell projects the active mode tokens onto the document; controls inherit them.

Discovery collects JSON files under src/themes/, including nested folders. Adding or changing source files needs regeneration and a build; selecting an available theme happens at runtime.

2. Switch light and dark

In Appearance, choose Light, Dark or System under Color scheme. Shell selects the corresponding map from the same theme; System follows the device preference.

PartExampleMeaning
ThemeOceanThe chosen palette and finish.
Color schemeDarkWhich variant to use.
NavigationSidebarThe app layout, independent of the palette.
Density / cornersCompact / SquareSeparate appearance preferences.

For values shared by both variants, add an optional shared map to the same JSON file:

"shared": {
  "radius": "0.75rem",
  "font-sans": "ui-sans-serif, system-ui, sans-serif"
}

This is a property to add, not another complete file. A mode-specific value overrides the same key in shared. An explicit user corner choice still applies. Fonts must already be available or loaded by the host; a theme does not download them.

3. Let users customize their theme

The built-in theme manager is part of Appearance:

  1. Choose a theme and open Customize theme beside the selector.
  2. Give the copy a name; adjust colors, radius, surface shadow or font family.
  3. Use the preview's Light / Dark controls to inspect both variants.
  4. Choose Save copy and apply. Later edits to that personal theme use Save and apply.

The preview affects only its sample controls and dialog. Cancel discards the draft. Bundled and app themes remain unchanged; saving creates a personal copy. Values the editor does not expose are retained in that copy.

Preview first, save when ready

Preview first, save when ready. Customize starts a local draft. Previewing or cancelling does not change the app. Saving an original creates and selects a personal copy; subsequent saves update that copy. Failed saves leave the draft available.
Customize starts a local draft. Previewing or cancelling does not change the app. Saving an original creates and selects a personal copy; subsequent saves update that copy. Failed saves leave the draft available.

Reset to default theme selects App default without deleting personal themes. Delete theme is available for personal themes and asks for confirmation. Deleting the active personal theme selects App default.

Share a theme as a file

Download theme beside the selector saves the theme shown in the preview (the open draft, otherwise the selected theme) as <name>.theme.json. Import theme opens such a file as an unsaved personal copy: check it in the preview, then choose Save copy and apply. Nothing is saved before that, and the copy gets a new ID, so it never replaces an existing theme.

Imported files may set only known theme tokens, and values may not load resources: url(), image functions, CSS escapes, ;, braces, <, > and @ are rejected with the location of the problem. Downloaded files reference the published JSON Schema in $schema, so editors and coding agents can validate a theme they write. To ship a downloaded theme with the app, copy the file into src/themes/.

4. Know what is saved

DataOwnerLifetime
src/themes/ocean.jsonApp sourceShips with the application; unchanged by the editor.
Selected theme and personal copiesshell.themeLibraryOne hidden user-scoped Core setting.
Light / Dark / System choiceshell.modeA device-scoped Core setting.
Unsaved edits and preview modeAppearance editorLocal draft; not applied to the app until saved.

Core uses the existing settings.storage contract. The default is browser storage; cross-device synchronization requires the host's server-backed storage and identity integration. There is no separate theme persistence layer and no browser write back to source files.

Next: customize the integration

You want to…Read
Register themes directly with TypeScriptProgrammatic themes
Add your own theme-selection controlActions and service
Change the default CSS for every visual pluginCentral CSS and tokens
Understand identities, persistence and startupTheme reference

On this page