One schema, unlimited themes
Stop writing dark: variants everywhere. Define your theme shape once, get full TypeScript inference, and scale from light/dark to multi-tenant.
import { createSchema, t, InferTheme } from '@livery/core'const schema = createSchema({definition: {primary: t.color(),background: t.color(),}})const theme: InferTheme<typeof schema.definition> = {primary: '#171717',background: '#ffffff',accent: '#f97316', // ❌ Type error}// Works in every theme<button className="bg-primary text-background">Click me</button>
The Problem vs The Solution
Stop duplicating styles for every theme. Use semantic tokens that just work.
Without Livery
Every component needs explicit styles for each theme.
- ✗Duplicate styles for every theme variant
- ✗Adding a third theme means editing every file
- ✗Easy to miss a dark: variant somewhere
- ✗No type safety — typos fail silently
- ✗Hard to maintain consistency across components
<div className="bg-white dark:bg-slate-900text-slate-900 dark:text-white"><button className="bg-blue-500 dark:bg-blue-400hover:bg-blue-600 dark:hover:bg-blue-500">Click me</button></div>
With Livery
Use semantic tokens that Livery resolves for you — at build time, on the server, or at runtime. Same markup works for any theme.
- Write styles once, works in every theme
- Add new themes without touching components
- Full TypeScript inference catches errors
- Runtime theme switching with no flash
- Scales from light/dark to multi-tenant
- Single source of truth for design tokens
<div className="bg-background text-foreground"><button className="bg-primary hover:bg-primary-hover">Click me</button></div>
How You'll Actually Use Livery
Four steps to type-safe, dynamic theming.
Define Schema
Describe your theme shape with TypeScript. Livery infers types from your schema automatically.
import { createSchema, t, InferTheme } from '@livery/core';export const schema = createSchema({colors: {primary: t.color(),background: t.color(),},});type Theme = InferTheme<typeof schema.definition>;
Create Themes
Define theme objects that match your schema. TypeScript ensures every theme has the right shape.
const light: Theme = {colors: { primary: '#171717', background: '#FFFFFF' },};const dark: Theme = {colors: { primary: '#FFFFFF', background: '#0A0A0A' },};
Map to Tailwind
Reference Livery CSS variables in your Tailwind config. Works with any CSS framework.
@theme {--color-primary: var(--colors-primary);--color-bg: var(--colors-background);}
Use in Components
Write your styles once using semantic tokens. They work automatically in every theme.
<button className="bg-primary text-white">Click me</button>
Livery generates CSS variables from your schema — at build time, on the server, or dynamically at runtime. Switch themes instantly with no flash.
See it in action
This entire page is themed with Livery. Click a theme below — fonts, borders, radii, shadows, everything changes.
Dashboard
Your personalized overview of recent activity and quick actions.
Quick Actions
Access your most used features instantly.
Analytics
Track your progress and growth metrics.
Typography Preview
Display Heading
Body text uses the sans-serif font stack. Clean and readable.
const theme = 'default';Current theme: Default — Clean, minimal design
Which package do you need?
Answer a few questions to find the right integration for your use case.
What kind of theming do you need?
Scales With You
And When You Need More
Start with light/dark mode. The same schema and type safety scales to multi-tenant, white-label, and beyond.
Multi-Tenant SaaS
Each customer gets their own brand colors, loaded from your database at runtime.
User Preferences
Let users customize their experience with validated, type-safe theme options.
A/B Testing
Test different visual treatments. Each variant is just a theme object.
White-Label Products
Ship one codebase that adapts to each customer's brand automatically.
Resolvers handle fetching and caching
Create a resolver to fetch themes from your API or database. Built-in caching keeps things fast, and TypeScript ensures the response matches your schema.
import { createResolver } from '@livery/core';const resolver = createResolver({fetcher: async ({ themeId }) => {const res = await fetch(`/api/themes/${themeId}`);return res.json();},cache: { ttl: 60_000 }, // 1 minute});const theme = await resolver.resolve({ themeId: 'acme-corp' });
Same schema. Same components. Any data source.
Livery Dashboard
A visual interface for managing your themes. Perfect for teams that want designers to create themes without touching code.