Skip to content
v2.5.0
English
Русский
Documentation

Themes

VegaUI ships a separate theme layer in @vega-ui/theme-core. The package entrypoint is packages/theme/src/index.css, which imports both light.css and dark.css.

Theme Model

Themes are class-based:

  • .light: light theme semantic values
  • .dark: dark theme semantic values

Each theme imports @vega-ui/tokens-core first, then maps raw token scales into semantic application variables such as:

  • --background-color
  • --text-color
  • --focus-color
  • --surface-primary
  • --surface-secondary
  • --border-color
  • --fills-primary
  • --label-primary

Key Semantic Variables

These are the main variables application code and component styles should rely on first.

VariableRoleTypical Consumers
--background-colorbase app canvas colorapp shell backgrounds, thumb-on-track contrast, custom page layout
--text-colordefault content colorText, Heading, field values, icon triggers, option rows
--focus-colorfocus outline colorbuttons, links, sliders, radios, checkboxes, segmented control
--surface-primaryprimary container surfaceDialogContent, DrawerContent, SheetContent, PopoverContent
--surface-secondaryfloating or transient surfaceSelectListbox, TooltipContent, TooltipArrow
--border-colordefault structural borderCard, Fieldset, TableRow, CheckboxCard
--border-color-hoverinteractive border emphasishover-state border progression for border-driven surfaces
--color-errorvalidation and error feedbackTextField, TextArea, PinField, HelperText
--fills-primaryaccent-selected fillchecked Checkbox, checked Radio, page control progress
--fills-secondaryneutral track or subdued fillProgress, Meter, MeterStack, Slider, Code, off Switch
--label-primarystrong indicator labelpage control active indicators and derived emphasis fills
--label-secondarymuted secondary textFieldset legend text, Code foreground, secondary metadata
--disable-text-colordisabled primary contentdisabled button text, disabled field values, disabled segmented items
--disable-background-colordisabled control surfacedisabled TextField, TextArea, Select, Checkbox, Switch, ButtonBase
--surface-shadowelevated surface shadowDialog, Drawer, Sheet, Popover, Tooltip, SelectListbox

Semantic Token Types

Key Semantic Variables is the quick-reference layer. This section is the classification layer: it groups theme variables by semantic family so the system is easier to extend and reason about.

TokenSemantic TypeMeaning In UI
--fills-primaryaccent-soft-fillprimary accent fill for selected or active UI
--fills-primary-hoveraccent-soft-fill-hoverhover state for the primary accent fill
--fills-primary-activeaccent-soft-fill-activeactive state for the primary accent fill
--fills-secondaryneutral-track-fillneutral fill for tracks, rails, and subdued containers
--fills-secondary-hoverneutral-track-fill-hoverhover state for neutral track-like surfaces
--fills-secondary-activeneutral-track-fill-activeactive state for neutral track-like surfaces
--fills-tertiaryfield-surfacedefault background for text-entry and field-like controls
--fills-tertiary-hoverfield-surface-hoverhover state for field surfaces
--fills-tertiary-activefield-surface-activeactive state for field surfaces
--fills-quaternary-hovermicro-surface-hoverlightweight hover fill for icon actions and micro-interactions
--fills-quaternary-activemicro-surface-activeactive state for micro-interactions
--surface-primarycontainer-surfacemain elevated surface for containers such as dialogs or sheets
--surface-secondaryfloating-surfacefloating surface for listboxes, popovers, and tooltips
--surface-shadowelevation-shadowshadow for elevated surfaces
--text-colorcontent-primarydefault readable content color
--text-color-inversecontent-on-accentreadable content color on accent-filled backgrounds
--label-primarylabel-strongstrong UI label or indicator color
--label-secondarylabel-mutedsecondary label or metadata color
--label-tertiarylabel-subtleplaceholder, helper, and subdued label color
--border-colorborder-defaultdefault structural border color
--border-color-hoverborder-hoverhover state for structural borders
--border-color-activeborder-activeactive state for structural borders
--disable-background-colordisabled-surfacedisabled background and surface fill
--disable-text-colordisabled-contentdisabled primary text or value color
--disable-label-text-colordisabled-subtle-contentdisabled secondary, helper, or placeholder text color
--disable-border-colordisabled-borderdisabled border or indicator color
--focus-colorfocus-ringfocus ring and outline color
--color-errorfeedback-errorerror and validation feedback color
--overlaybackdrop-overlaybackdrop tint for modal and overlay layers
--separator-opaqueseparator-strongvisible separators, handles, and dividers

Semantic Families

Think about theme variables in these families first:

  • fills-*: interactive fill semantics for selected, hovered, pressed, or field-like surfaces
  • surface-*: container and floating surface semantics for overlays and elevated UI
  • text-* and label-*: readable content and UI-label semantics
  • border-*: structural boundary and border-emphasis semantics
  • disable-*: disabled-state surface, content, and border semantics
  • focus-color, color-error, overlay, separator-*: system and state-feedback semantics

Semantic Types In Practice

These semantic types come from how the UI package actually uses the variables:

  • accent-soft-fill: selected or checked states such as Checkbox, Radio, and page-control progress
  • neutral-track-fill: tracks and neutral bars such as Progress, Meter, MeterStack, and Slider
  • field-surface: input-like controls such as TextField, TextArea, PinField, and SelectCombobox
  • micro-surface-*: low-emphasis hover and active surfaces for icon buttons, trigger buttons, and picker items
  • container-surface: primary overlay or panel surfaces such as DialogContent, DrawerContent, and SheetContent
  • floating-surface: floating content surfaces such as SelectListbox, TooltipContent, and PopoverContent
  • content-*, label-*, border-*, and disable-*: text, label, border, and disabled-state semantics reused across the entire kit

Use this section to answer “what class of semantic token do I need here?”, then use Key Semantic Variables to pick the concrete variable name.

How To Apply A Theme

Put the theme class on <html>:

<html class="light">

Dialog, Drawer and Sheet render into document.body, so a class on an inner wrapper does not reach them.

App Shell With Theme Switcher

import { useEffect, useState } from 'react';
import { Button } from '@vega-ui/react';

type ThemeName = 'light' | 'dark';

export function ThemeSwitcher() {
  const [theme, setTheme] = useState<ThemeName>('light');

  useEffect(() => {
    document.documentElement.classList.remove('light', 'dark');
    document.documentElement.classList.add(theme);
  }, [theme]);

  return (
    <Button variant='secondary' appearance='ghost' onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Switch to {theme === 'light' ? 'dark' : 'light'}
    </Button>
  );
}

Give the page the theme background and text color:

body {
  background: var(--background-color);
  color: var(--text-color);
}

A wrapper with another theme class themes its own subtree, but Dialog, Drawer and Sheet opened from it still take the theme of <html>.

Theme Customization

The safest customization path is to define your own theme class and override semantic variables there.

@import '@vega-ui/theme-core';

.brand-sunrise {
  --background-color: #fffaf3;
  --text-color: #251b10;
  --surface-primary: #fffdf9;
  --surface-secondary: #fff7ec;
  --border-color: #e7d7c2;
  --focus-color: var(--color-orange-accent-400);
  --fills-primary: color-mix(in oklab, var(--color-orange-accent-500) 16%, transparent);
  --fills-primary-hover: color-mix(in oklab, var(--color-orange-accent-500) 22%, transparent);
  --label-secondary: #7a6653;
}

Then put that class on <html> the same way as .light or .dark.

Customize semantic variables first. Reach for component-local variables only when the change should affect one component family rather than the whole application.

Light vs Dark Behavior

Light and dark themes keep the same semantic variable names. The main difference is how those semantics map onto the raw token scale.

In particular, accent colors are inverted in dark mode. For example, --color-blue-accent-100 points to a lighter raw blue in light mode and a darker raw blue in dark mode. That is why component styles should prefer semantic accent variables over raw palette steps.

Guidance

  • put the theme class on <html>, not on a wrapper or per component
  • consume semantic variables in component CSS whenever possible
  • avoid writing component-specific light/dark overrides when a semantic theme variable already exists
  • customize semantic variables before touching component-local --component-* variables