BitaKit
API reference

Theme reference

Theme definitions, manager Actions, storage and the central CSS contract.

Start with Themes and appearance to add one theme and use the built-in editor. The APIs here describe the current checkout; use matching package builds.

Definition and identity

@bitakit/shell exports themeSchema, ShellThemeInput, ShellThemeDefinition and ThemeEntry.

FieldRequiredMeaning
schemaVersionYes1.
idYesNonempty identity within its source; not inferred from the filename.
nameYesDisplay name; need not be unique.
sharedNoCSS variable values for both modes, default {}.
lightNoLight-mode overrides, default {}.
darkNoDark-mode overrides, default {}.

Token maps contain string values and omit -- from names. themeSchema checks the object shape; it does not prove CSS validity or accessible color contrast. Use foreground/background pairs and check your controls in both modes.

SourceExample keyEditable in Appearance?
App CSS defaultshell:defaultSave a personal copy.
App themeapp:oceanSave a personal copy.
Personal themeuser:my-oceanUpdate or delete.

themeKey(source, id) builds a key. Duplicate IDs within a source are errors; equal display names or IDs across different sources remain distinct. If the saved selection is no longer in the catalog, the active theme falls back to shell:default.

Theme files

Files from downloads, other apps or coding agents use a stricter schema than source themes, because their values are applied to the document root:

ExportPurpose
parseThemeFile(text)Parse file text into a ShellThemeDefinition; throws an Error naming the first problem.
stringifyThemeFile(theme)The downloadable file: the definition plus $schema, without source or key.
themeFileSchemaThe Zod schema parseThemeFile applies; an optional $schema is accepted and dropped.
themeFileSchemaUrlThe published JSON Schema URL written into $schema.

Token names must be variables of the central theme contract. Values may not contain url(, image(, image-set(, src(, element( or cross-fade( in any letter case, or a backslash, ;, {, }, <, > or @. The same rules ship as a JSON Schema in @bitakit/shell/theme.schema.json. Source themes in src/themes/ and the themes option are trusted app code and keep themeSchema, which also accepts and drops $schema, so a downloaded file can be copied into src/themes/ unchanged.

Programmatic themes

Under the Appearance category, Themes (/settings/theme) contains color scheme, theme selection and the copy editor. Layout (/settings/layout) contains navigation, density and notification position.

Themes includes App default, BitaKit Default, Warm Sand, Soft Rose, Calm Sage, High Contrast, Slate Flat, Ocean Blue, Golden Amber and Forest Green without app configuration. Each bundled alternative provides light and dark colors through the existing semantic tokens. App default remains the initial selection and Reset target, adding no token overrides. BitaKit Default uses its own explicit palette under shell:bitakit. Calm Sage uses rounded, flat controls; High Contrast uses square corners and stronger borders; Slate Flat removes gradients and raised surface/control shadows. Original definitions stay immutable; save a personal copy to customize them. Ocean Blue, Golden Amber and Forest Green retain the rounded, raised BitaKit finish with different palettes.

Display spans the top of Themes. Theme selection and the copy editor sit beside a live preview on wide screens and stack on narrow screens. The preview follows selection and unsaved edits; its light/dark control never changes the saved app mode. Radius, surface shadow and button finish use standard selection cards with visual samples; font options show their typeface. Flat button finish removes control depth while retaining surface shadows and palette colors.

File discovery and explicit composition feed the same Shell theme catalog. For a manually composed app, pass definitions to the existing Shell plugin:

// Inside your existing client composition
shellPlugin({
  themes: [
    {
      schemaVersion: 1,
      id: 'ocean',
      name: 'Ocean',
      light: { primary: '#075985', 'primary-foreground': '#ffffff' },
      dark: { primary: '#7dd3fc', 'primary-foreground': '#082f49' },
    },
  ],
});

Import shellPlugin from @bitakit/shell. This fragment configures that plugin; it does not mount a second provider. See Manual composition.

In a discovery-enabled app, src/themes/**/*.json is the simpler route. Alternatively, provide the themes option in the app's existing src/configs/shell.ts:

import { defineConfig } from '@bitakit/core';
import type { ShellOptions } from '@bitakit/shell';

export default defineConfig({
  defaults: {
    themes: [{ schemaVersion: 1, id: 'soft', name: 'Soft', shared: { radius: '1rem' } }],
  } satisfies ShellOptions,
});

Merge this option into an existing config instead of replacing its other options. Discovery combines configured themes and discovered JSON files. Do not register the same app ID in both places. Packaged integrations can supply definitions through this option; automatic JSON collection is for the app source directory, not every plugin's folder.

Actions and service

Use existing Actions for user-facing controls. They retain validation, pending state, availability guards and the standard confirmation flow:

// src/plugins/branding/ui/choose-theme.tsx
'use client';
import { ActionButton } from '@bitakit/ui';

export function ChooseOcean() {
  return (
    <ActionButton action="shell.selectTheme" input={{ key: 'app:ocean' }}>
      Use Ocean
    </ActionButton>
  );
}

The Ocean theme must already exist. No new theme.set Action or separate state store is needed.

Action IDInputResult
shell.selectTheme{ key: 'app:ocean' }Select a known theme.
shell.saveThemeCopyFull ShellThemeInput with a fresh personal idSave and select the copy.
shell.updateThemeFull ShellThemeInput with an existing personal idReplace that personal definition; keep the selection.
shell.deleteTheme{ id: 'my-ocean' }Confirm, then delete a personal theme.

For integration code, Shell provides the typed themeService contract (themes):

MethodPurpose
list() / active()Read the catalog or current theme.
canWrite()Check readiness, storage writability and pending state.
select(key) / saveCopy(theme)Select a theme or create and select a personal copy.
update(theme) / remove(id)Change or delete a personal definition.
subscribe(listener)Observe library changes; returns cleanup.

Use the existing getService(services, themeService) or React useService(themeService) after runtime readiness. Direct service calls do not run Action confirmation or execution guards. Writes reject unknown/duplicate identities or unavailable storage; updates and deletes reject original themes. Writes return Core's SaveResult (saved, superseded, cancelled); only saved confirms persistence. An Action wraps that value in its execution result.

Central CSS and tokens

The central stylesheet supplies defaults; the active JSON theme overrides only its declared tokens on <html>. This also reaches portaled menus, dialogs and notifications. Switching themes or unmounting removes the previous runtime overrides.

LayerResponsibility
Central host CSSBase appearance shared across the app.
Theme sharedOverrides used in either mode.
Theme light or darkOverrides shared for that mode.
Appearance preferencesSeparate layout, density and corner choices.

For a host-owned baseline, keep the generated CSS entry and import your file after it:

/* src/app/globals.css — preserve your app's existing entry and import paths */
@layer theme, base, components, utilities;
@import 'tailwindcss';
@import '../bitakit.gen.css';
@import './theme.css';
/* src/app/theme.css */
:root {
  --primary: #075985;
  --primary-foreground: #ffffff;
}
:root.dark {
  --primary: #7dd3fc;
  --primary-foreground: #082f49;
}

Use this for app defaults; use a JSON definition for a theme users can select and copy. App default adds no runtime token overrides, so the app CSS remains visible. Menu samples and previews read the app base tokens for both color schemes before applying theme overrides.

CustomizeRepresentative tokens
Colors--background, --foreground, --primary, --primary-foreground, --card, --border, --ring
Sidebar--light--sidebar, --dark--sidebar and their foreground/accent/border variants
Typography--font-sans, --font-heading; host supplies font assets
Shape and strokes--radius, --corner-shape, --border-width, --focus-ring-width
Depth--theme-shadow-surface, --theme-shadow-overlay, --theme-shadow-control, --theme-shadow-field
Motion--theme-motion-duration, --theme-motion-ease

JSON uses these names without --. A shadow value of none is projected as a transparent shadow to preserve composed border/focus rings. Explicit corner choices and functional round controls remain meaningful. External widgets need an adapter to these tokens; installing a widget does not automatically make it theme-compatible.

CSS presets and packaged styles

An optional CSS preset is separate from a selectable JSON theme. Existing integrations declare presets: [{ id: 'ocean', label: 'Ocean' }] in Shell options and ship rules under :root[data-theme-preset='ocean'] and its .dark variant. This adds shell.preset to Appearance; without configured presets, that setting does not exist.

Prefer JSON themes for new selectable palettes with personal copies. Do not register the same palette twice: runtime JSON tokens override normal stylesheet declarations for the same tokens.

A distributable CSS theme uses the package's bitakit.theme stylesheet entry. Export the stylesheet and ./package.json; declare the visual packages it maps. Discovery collects these styles in the generated root CSS. Ordinary bitakit.styles entries are component styles and cannot redefine the platform baseline.

Storage and first paint

shell.themeLibrary atomically stores { selected, custom } as a hidden user-scoped setting. shell.mode is device-scoped. Core owns confirmed values and delegates persistence to settings.storage; the theme service does not keep a second confirmed store.

The Shell theme provider waits for loaded settings, follows system mode changes and projects the selected tokens. The built-in editor keeps unsaved drafts and its preview mode local. Saving an original creates a copy; saving an existing personal theme updates it. Cancel leaves the confirmed theme unchanged.

ShellThemeScript in the document head applies the browser-stored color scheme before hydration. Generated Shell apps already include it. It does not pre-load personal theme tokens; those apply after settings/identity resolution. With custom server-backed storage, the script uses the system scheme until settings load. Custom hosts can supply a CSP nonce and retain suppressHydrationWarning on <html>.

If the theme does not appear

SymptomCheck
No Appearance pageShell and App Preferences are installed, enabled and the Settings route is mounted.
New JSON file is missingIt is under the configured app source's themes/; regenerate and rebuild.
Duplicate identity errorThe same id is not present twice in the app source/options.
Some UI keeps its old colorsIt uses central semantic tokens, not hardcoded colors or an unmapped external library.
Preview changes but the app does notSave and apply; previewing alone is deliberately local.
Changes disappear on another deviceBrowser storage is local; shared persistence requires the host's storage integration.

On this page