Skip to content

Theme Configuration

How the theme system works and how to configure it in each package. See Installation for setup steps.


Theme Interface

Every theme object contains the following token groups:

ts
interface Theme {
  mode: 'light' | 'dark' | 'auto';
  colors: ThemeColors;
  spacing: ThemeSpacing;
  typography: ThemeTypography;
  shadows: ThemeShadows;           // CSS box-shadow strings (web)
  shadowDefinitions: Record<string, ShadowLayer[]>; // structured (cross-platform)
  radius: ThemeRadius;
  transitions: ThemeTransitions;
  zIndex: ThemeZIndex;
}
Token groupWeb (CSS custom properties)React NativeDocs
colors--color-*theme.colors.*Web · RN
spacing--space-*theme.spacing.* (rem → px)Web · RN
typography--font-family-*, --font-size-*, --font-weight-*, --line-height-*theme.typography.*RN
shadows--shadow-*Use shadowDefinitions insteadWeb
shadowDefinitionsshadowToRN(theme.shadowDefinitions.md)RN
radius--radius-*theme.radius.* (rem → px)Web · RN
transitions--duration-*, --ease-*theme.transitions.* (parse for Animated)Web · RN
zIndex--z-*theme.zIndex.*Web · RN

Colors

Colors are the primary customisation point. Grundtone uses semantic color tokens — names like primary, background, text — instead of raw hex values. Your app uses these names; the actual colors come from the theme you configure.

Semantic Color Keys

TokenPurpose
primary, primaryLight, primaryDark, onPrimaryPrimary actions, links, buttons
secondary, secondaryLight, secondaryDarkSecondary actions
success, successLight, successDark, warning, warningLight, warningDark, error, errorLight, errorDark, info, infoLight, infoDarkStatus and feedback
background, backgroundAlt, surface, surfaceAlt, surfaceRaised, surfaceOverlayBackgrounds
text, textSecondary, textTertiary, textInverse, textPlaceholder, textDisabledText colors
borderLight, borderMedium, borderStrong, borderInverseBorders
focus, focusRingFocus states
neutralNeutral UI elements

See Colors for the full token reference with swatches.


createTheme()

All packages use createTheme() from @grundtone/core to build themes. Override only the colors you need; everything else (spacing, typography, shadows, radius, transitions, z-index) uses defaults.

ts
import { createTheme } from '@grundtone/core';

const { light, dark } = createTheme({
  light: {
    primary: '#0059b3',
    primaryLight: '#3381cc',
    primaryDark: '#003a7a',
    onPrimary: '#ffffff',
    // Override any semantic color
  },
  dark: {
    primary: '#4dabf7',
    primaryLight: '#74c0fc',
    primaryDark: '#339af0',
    onPrimary: '#121212',
  },
});

The returned light and dark objects are complete Theme instances with all token groups populated.


Shadows: Structured Definitions

Shadows use a structured, cross-platform representation. Each shadow level (xs–2xl, inner) is defined as an array of ShadowLayer objects:

ts
interface ShadowLayer {
  x: number;       // offset-x (px)
  y: number;       // offset-y (px)
  blur: number;    // blur-radius (px)
  spread: number;  // spread-radius (px)
  color: string;   // hex, e.g. '#000000'
  opacity: number; // 0–1
  inset?: boolean;
}

The theme.shadows object contains pre-built CSS box-shadow strings derived from these definitions — web consumers use it as before. The theme.shadowDefinitions object contains the structured data for cross-platform use:

FieldTypePlatform
theme.shadows.mdstring (CSS box-shadow)Web
theme.shadowDefinitions.mdShadowLayer[]Any

React Native converts structured layers to native shadow styles via shadowToRN().


Non-Color Tokens

The following token groups use sensible defaults. To customise them for a self-hosted fork, edit the corresponding export in packages/core/src/theme-preset.ts and rebuild:

ExportControls
defaultSpacing8px-grid spacing scale (xs–4xl)
defaultTypographyFont families, sizes, weights, line heights
defaultShadowDefinitionsStructured shadow layers (xs–2xl, inner)
defaultRadiusBorder radius scale (none–full)
defaultTransitionsDuration and timing functions
defaultZIndexZ-index layer stack

Changing the Default Colors (Self-Hosting)

If you fork or self-host Grundtone, you can change the system-wide defaults so every app that imports @grundtone/core gets your brand colors out of the box — without needing createTheme() overrides at the app level.

Edit the two preset objects in packages/core/src/theme-preset.ts:

ts
// packages/core/src/theme-preset.ts

export const defaultColorPreset: ColorPreset = {
  primary: '#your-brand', // ← change these
  primaryLight: '#your-brand-light',
  primaryDark: '#your-brand-dark',
  onPrimary: '#ffffff',
  // ... rest of light-mode tokens
};

export const defaultColorPresetDark: ColorPreset = {
  primary: '#your-brand-dark-mode',
  // ... rest of dark-mode tokens
};

After editing, rebuild the packages (pnpm build). Every downstream package — design-tokens, Vue, Nuxt, React Native — derives its defaults from these objects, so the change propagates automatically.

Tip: If you only need to override colors in a single app (not system-wide), use createTheme() at the app level instead. See Web or React Native for per-app setup.


Web vs. React Native

PlatformHow tokens work
Web (Vue, Nuxt, Plain Web)Theme is applied as CSS custom properties on :root. Use var(--color-*), var(--shadow-*), etc. or rely on utility classes.
React NativeNo CSS. Tokens come from the theme object via useGrundtoneTheme(). Use theme.colors.primary, shadowToRN(theme.shadowDefinitions.md), etc. in style props.

Summary

PackageWhere to configureDocs
VueThemeProvider in App.vueWeb Colors & Theming
Nuxtnuxt.config.tsgrundtone.themeWeb Colors & Theming
React NativeGrundtoneThemeProvider in AppReact Native Colors
Plain WebYour own CSS / SCSSWeb Colors & Theming