Semantic Tokens & Typography
@themvora/core — the token system
Themvora isn't another dark-mode library. Every theme is a complete design system — colors, typography, spacing, radius, shadows, border widths, z-index and breakpoints — flattened into semantic CSS variables that swap in one atomic update.
1Define every group in one theme
import { defineTheme } from "@themvora/core";
const brand = defineTheme({
name: "brand-light",
tokens: {
colors: {
// Surfaces
background: "#ffffff",
foreground: "#0f172a",
card: "#ffffff",
cardForeground: "#0f172a",
popover: "#ffffff",
popoverForeground: "#0f172a",
// Brand
primary: "#6d28d9",
primaryForeground: "#ffffff",
secondary: "#ede9fe",
secondaryForeground: "#3b0764",
// Support
muted: "#f5f3ff",
mutedForeground: "#6b7280",
accent: "#ede9fe",
accentForeground: "#3b0764",
// Status
destructive: "#dc2626",
destructiveForeground: "#ffffff",
success: "#16a34a",
successForeground: "#ffffff",
// Lines
border: "#e5e7eb",
input: "#e5e7eb",
ring: "#6d28d9",
},
typography: {
fontFamilies: { sans: "Inter, system-ui, sans-serif" },
fontSizes: { base: "1rem", "4xl": "2.25rem" },
lineHeights: { normal: "1.5" },
},
spacing: { "1": "4px", "4": "16px", "8": "32px" },
radius: { md: "8px", xl: "16px", full: "9999px" },
shadows: { md: "0 4px 6px rgba(0,0,0,0.07)" },
borderWidths: { "1": "1px", "2": "2px" },
zIndex: { "10": "10", "50": "50" },
breakpoints: { md: "768px" },
},
});
// themeToCSSVariables(brand) emits --theme-color-primary,
// --theme-typography-font-size-4xl, --theme-spacing-4,
// --theme-radius-full, --theme-border-width-2, --theme-z-index-10 and more.Adapters and build-time integrations consume the same resolved tokens — @themvora/tailwind maps them to --color-*, --radius-*, --spacing-*, --font-* and --shadow-* utilities.
Every theme validates against the full semantic color set — the base surfaces ( background, foreground, card, popover), interaction colors ( primary, secondary, accent, muted), and the status colors ( destructive, success) — each with its own *Foreground counterpart, plus border, input and ring. Missing any of them fails validateTheme.
2Token references & derived values
import { defineTheme, resolveTokens } from "@themvora/core";
const brand = defineTheme({
name: "brand",
tokens: {
colors: {
primary: "#6d28d9",
// Reference using $ prefix
primaryHover: "$colors.primary",
// Or using { } braces
ring: "{colors.primary}",
// Nested references work too
surface: {
primary: "$colors.primary",
hover: "{colors.primaryHover}",
},
},
spacing: {
"4": "16px",
// Expressions with calc()
"8": "calc($spacing.4 * 2)",
},
},
});
// Resolve all references to concrete values
const resolved = resolveTokens(brand.tokens);
// resolved.colors.primaryHover === "#6d28d9"
// resolved.spacing["8"] === "32px"import { defineTheme, resolveTokens } from "@themvora/core";
const brand = defineTheme({
name: "brand",
tokens: {
colors: {
primary: "#6d28d9",
// Returns black or white based on WCAG luminance
primaryForeground: "contrast(#6d28d9)",
// Auto-derives readable foreground from base token
secondary: "#ede9fe",
secondaryForeground: "auto()",
// Works with references too
accent: "$colors.primary",
accentForeground: "auto($colors.primary)",
},
},
});
const resolved = resolveTokens(brand.tokens);
// resolved.colors.primaryForeground === "#ffffff" (white on purple)
// resolved.colors.secondaryForeground === "#1e1b4b" (dark on light purple)import {
defineTheme,
extendTheme,
composeTheme,
mergeTokens,
} from "@themvora/core";
// Base theme with common tokens
const base = defineTheme({
name: "base",
tokens: {
colors: {
background: "#ffffff",
foreground: "#0f172a",
primary: "#6366f1",
},
radius: { md: "8px", lg: "12px" },
spacing: { "4": "16px", "8": "32px" },
},
});
// Extend: create a variant with overrides
const brandLight = extendTheme("brand-light", base, {
meta: { family: "brand", mode: "light" },
tokens: {
colors: {
primary: "#6d28d9",
accent: "#f5f3ff",
},
radius: { lg: "16px" },
},
});
// Compose: merge multiple themes into one
const combined = composeTheme("combined", base, brandLight, {
tokens: { shadows: { md: "0 4px 6px rgba(0,0,0,0.1)" } },
});
// Low-level: merge token maps directly
const customTokens = mergeTokens(brandLight.tokens, {
colors: { destructive: "#ef4444", success: "#16a34a" },
code: { background: "#f8f8f8" },
});3Token helpers — complete examples
import { flattenTokens } from "@themvora/core";
const tokens = {
colors: { primary: "#6366f1", surface: { hover: "#ede9fe" } },
spacing: { "4": "16px" },
};
const flat = flattenTokens(tokens);
// {
// "colors.primary": "#6366f1",
// "colors.surface.hover": "#ede9fe",
// "spacing.4": "16px",
// }import { resolveTokens } from "@themvora/core";
const tokens = {
colors: {
primary: "#6366f1",
ring: "$colors.primary",
surface: { primary: "{colors.primary}" },
},
};
const resolved = resolveTokens(tokens);
// {
// colors: {
// primary: "#6366f1",
// ring: "#6366f1",
// surface: { primary: "#6366f1" },
// },
// }import { themeToCSSVariables } from "@themvora/core";
const vars = themeToCSSVariables(brand);
// {
// "--theme-color-primary": "#6d28d9",
// "--theme-color-secondary": "#ede9fe",
// "--theme-typography-font-family-sans": "Inter, system-ui, sans-serif",
// "--theme-spacing-4": "16px",
// "--theme-radius-lg": "12px",
// "--theme-code-background": "#161b22",
// "--theme-code-keyword": "#ff7b72",
// ...
// }
// Use with groups option for subset
const colorVars = themeToCSSVariables(brand, { groups: ["colors"] });
const codeVars = themeToCSSVariables(brand, { groups: ["code"] });import { validateTheme, getBuiltInThemes } from "@themvora/core";
const customTheme = defineTheme({
name: "custom",
tokens: { colors: { primary: "#6366f1" } }, // Missing required colors
});
const result = validateTheme(customTheme, { themes: getBuiltInThemes() });
// {
// valid: false,
// issues: [
// { type: "missing", path: "colors.background", message: "Required color token missing" },
// { type: "missing", path: "colors.foreground", message: "Required color token missing" },
// ...
// ]
// }import { generateTheme } from "@themvora/core";
// Generate complete light/dark pair from one seed color
const { light, dark } = generateTheme({
seed: "#6366f1", // Base brand color
family: "indigo", // Family name
// Auto-generates: primary, secondary, muted, accent, border, ring
// Plus full typography, spacing, radius, shadows scales
// And harmonized code tokens for syntax highlighting
});
console.log(light.name); // "indigo-light"
console.log(dark.name); // "indigo-dark"
console.log(light.tokens.code); // Full code token set4Full token reference
Semantic colour roles — each paired with its own foreground.
--theme-color-{key}| background | #eff0f1 |
| foreground | #1a1b1c |
| muted | #e5e6e7 |
| mutedForeground | #636465 |
| card | #f9f9fb |
| cardForeground | #1a1b1c |
| popover | #f4f5f6 |
| popoverForeground | #1a1b1c |
| primary | #0d162e |
| primaryForeground | #fbfcfd |
| secondary | #e1e2e4 |
| secondaryForeground | #232425 |
| accent | #897fa7 |
| accentForeground | #111213 |
| destructive | #bb0916 |
| destructiveForeground | #fbfcfd |
| success | #00782d |
| successForeground | #fbfcfd |
| border | #d8d9db |
| input | #858687 |
| ring | #0d162e |
Keys are appended to their group prefix exactly as written, so colour keys stay camelCase — --theme-color-primaryForeground, not --theme-color-primary-foreground. Spacing and border widths are numbered, so the token is --theme-spacing-4, not --theme-spacing-md.
Looking for syntax-highlighting tokens? Those have their own page — Code Tokens.