Getting StartedSetup and authoring guides 3
ToolsStarters and interactive helpers 2
ReferenceAPIs, styling contracts, and runnable examples 3

Creating a new theme

The safest workflow is to start from a built-in preset, keep the primitive token scales intact, and override semantic values where product branding or interaction tone needs to change.

1. Choose a base preset

Pick the preset that feels closest to the product surface you want. That gives you a complete token contract before you make any product-specific decisions.

Start from a preset

ts
import { createTheme, defaultThemePreset, type ThemeDefinition } from '@tailng-ui/theme';

export const productTheme: ThemeDefinition = createTheme(defaultThemePreset, {
  meta: { name: 'acme-product', mode: 'light' },
  tokens: {
    semantic: {
      accent: {
        brand: '#2563eb',
        brandHover: '#1d4ed8',
      },
      focus: {
        ring: '#2563eb',
      },
    },
  },
});

2. Ship light and dark as a pair

In practice, teams usually author a matched light and dark pair. Keep the semantic intent the same across both so components and utilities can switch mode without changing their markup.

Create matching light and dark themes

ts
import {
  createTheme,
  defaultDarkThemePreset,
  defaultThemePreset,
  type ThemeDefinition,
} from '@tailng-ui/theme';

export const acmeLightTheme: ThemeDefinition = createTheme(defaultThemePreset, {
  meta: { name: 'acme-light', mode: 'light' },
  tokens: {
    semantic: {
      accent: { brand: '#2563eb', brandHover: '#1d4ed8' },
      focus: { ring: '#2563eb' },
    },
  },
});

export const acmeDarkTheme: ThemeDefinition = createTheme(defaultDarkThemePreset, {
  meta: { name: 'acme-dark', mode: 'dark' },
  tokens: {
    semantic: {
      accent: { brand: '#60a5fa', brandHover: '#3b82f6' },
      focus: { ring: '#60a5fa' },
    },
  },
});

3. Validate the contract before shipping

If you build or merge themes programmatically, validate them in tests so missing scales are caught before they reach runtime styling or Tailwind export paths.

  • Prefer semantic overrides for brand, focus, and surface work.
  • Keep token names stable so downstream wrappers do not need custom branches.
  • Run contract validation in tests when themes are generated dynamically.

Validate the theme contract

ts
import { isThemeContractValid, listMissingRequiredThemeScales } from '@tailng-ui/theme';

if (!isThemeContractValid(productTheme)) {
  console.error(listMissingRequiredThemeScales(productTheme));
}