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));
}