Navigated to /docs/ui/theming

Theming

Resolve a small brand configuration into light and dark color modes, product scales, typography, and interaction tokens.

Start from one brand color

Only color.light.primary is required. Add the published schema for editor completion, choose system as the default unless the product requires a fixed initial mode, and introduce overrides only after reviewing generated output.

JSON
json{
  "$schema": "./node_modules/@tavojs/ui/schema.json",
  "defaultTheme": "system",
  "color": {
    "light": { "primary": "#006ecf" }
  }
}

Separate product shape from color behavior

Presets provide coordinated defaults for scale, typography, density, and surfaces. Explicit config is merged afterward and always wins. Color methods decide how the brand anchors become ramps and structural colors.

  • minimal: neutral, general-purpose starting point.

  • enterprise and dense: practical dashboard density and restrained surfaces.

  • editorial and mobile: typography or touch-oriented foundations.

  • glass: translucent structural surfaces and backdrop-filter tokens.

  • monochrome: strict black, white, and neutral structure with exact action colors.

  • analogous, monochromatic, and glass are color generation methods; they are not component variants.

Control light, dark, and system modes

The tavoUi plugin reads tavo-ui.config.json and injects the generated CSS at build time. It does not import that JSON into the browser or create runtime state. Create one controller at the client application boundary, using the same initial mode as defaultTheme in the config.

TS
tsimport {
  createThemeController,
  mountThemeController
} from "@tavojs/ui/theme";

export const theme = createThemeController("system");
export const stopThemeRuntime = mountThemeController(theme);

export function setThemeMode(mode: "light" | "dark" | "system") {
  theme.setMode(mode);
}

export function toggleThemeMode() {
  theme.toggleMode();
}

Explicit light or dark mode writes data-tavo-theme on the document root. System mode removes that attribute so the generated prefers-color-scheme rules decide. The controller persists the user's selection. Call setThemeMode from a menu, button, or settings controller, and subscribe to theme.store only when UI needs to display the current selection.

Configure every theme layer intentionally

TavoUiThemeConfig requires a light primary color and exposes optional settings for presets, modes, scale, typography, interaction, viewport behavior, breakpoints, output selectors, accessibility, and token overrides.

  • color.light.primary is required. secondary is derived when omitted; dark anchors inherit light values and can override either color.

  • semantic defines success, warning, danger, and info globally or per light and dark mode.

  • scale controls dimensions and effects; typography controls font stacks and type sizes; interaction controls hover, focus, pressed, disabled, and transition behavior.

  • viewport emits a fluid root font-size; breakpoints emit responsive threshold data; output controls selectors and the system-mode media query.

  • tokens provides final per-mode token overrides; accessibility enables contrast diagnostics.

TS
tstype TavoUiThemeConfig = {
  preset?: "minimal" | "glass" | "enterprise" | "editorial" | "dense" | "mobile" | "monochrome";
  defaultTheme?: "light" | "dark" | "system";
  color: ThemeColorConfig;
  semantic?: ThemeModeSemanticConfig;
  scale?: ThemeScaleConfig;
  typography?: ThemeTypographyConfig;
  interaction?: ThemeInteractionConfig;
  viewport?: ThemeViewportConfig;
  breakpoints?: ThemeBreakpointsConfig;
  output?: ThemeOutputConfig;
  accessibility?: ThemeAccessibilityConfig;
  tokens?: ThemeTokenOverrides;
};

Follow the theme resolution order

  • Apply the selected preset first.

  • Deep-merge explicit color, dark color, scale, typography, viewport, breakpoints, semantic modes, output, accessibility, and token maps over the preset.

  • Derive a secondary anchor when one is absent, then build primary, secondary, and neutral ramps.

  • Generate semantic and product-alias tokens for both modes.

  • Apply per-mode token overrides last. Explicit overrides win even when they replace a generated alias.

Understand scale and interaction defaults

Without a preset or explicit base unit, comfortable density starts from a 40px medium control, 8px spacing base, 8px radius, 3px focus ring, 16px body type, and 22px blur. Derived tokens convert these values to rem where appropriate.

  • unit derives control height at 5×, spacing at 1×, radius at 1×, body type at 2×, and blur at 2.75× unless a more specific value is present.

  • density changes generated defaults only. Explicit unit, controlHeight, and spacing remain exact.

  • controlRadius and surfaceRadius override the shared radius for controls and surfaces independently.

  • motion and interaction.transition are multipliers; zero produces zero-duration generated transitions.

  • opacity, glassAlpha, hoverShadow, focusAlpha, and disabledOpacity are ratios from zero to one.

Fail close to invalid theme values

  • Positive-only values include unit, control height, spacing, type sizes, viewport dimensions, and breakpoints.

  • Radii, shadows, borders, focus, motion, blur, hover lift, active scale, and transition must be non-negative.

  • Viewport rootMin must not exceed rootMax, and minWidth must be less than maxWidth.

  • Breakpoints must be positive and strictly ascending: sm < md < lg.

  • Unknown presets, methods, modes, densities, and contrast levels throw descriptive errors.