Theming
How it works
Every component reads semantic tokens only (--accent, --fg, --bg-canvas, --radius-panel…), which resolve from a two-tier scale: a raw foundation palette (--n-0 … --n-1000) mapped to roles. A theme just re-points those variables.
ThemeProvider applies a config on mount by setting the variables on <html> — the same runtime mutation the live Configurator (top-right) performs. Because it's just CSS variables, a theme cascades to every component instantly, with zero rebuilds.
Predefined Theme Presets
Shavin UI ships with 17 curated theme presets out of the box. Each preset bundles an accent color, gray scale family, radius, panel background style, and display/body typography pairing.
Smart Accent Token System
When you set an accent color (preset or hex), @shavin/ui dynamically computes a coordinated cluster of WCAG AAA accessible CSS custom properties:
--accentBase brand accent RGB triplet (e.g. 62 99 221).
--accent-hoverAutomatically mixed +12% lighter in dark mode or -12% darker in light mode for interactive hover states.
--accent-fgOptimal text/icon foreground on solid accent buttons (10 10 12 or 255 255 255) via relative luminance.
--accent-contrast / --accent-textAccessible accent text color guaranteeing contrast on soft surfaces (e.g. Lime darkened on white, Monochrome inverted in dark mode).
Config properties
accentColor24 named presets (Monochrome, Grass, Blue, Iris…) or a literal #hex
grayColorZinc · Slate · Gray · Neutral · Stone — the neutral family
appearancelight · dark (toggled via the .dark class — app-controlled)
radiusnone · small · medium · large · full
scaling90 · 95 · 100 · 105 · 110 (%) — density via root font-size
panelBackgroundsolid · translucent — popover/menu/dialog surfaces
displayFont / bodyFontheading & body font presets
Declare a theme in code
Mount ThemeProvider once (e.g. at the app root, or drop it in bare as a fire-and-forget config step). This is exactly what the Configurator's Copy Theme button emits.
import { ThemeProvider } from "@shavin/ui";
export function App({ children }) {
return (
<ThemeProvider
accentColor="Grass" // preset name or "#rrggbb"
grayColor="Slate"
radius="medium"
scaling={100}
panelBackground="solid"
displayFont="Space Grotesk"
bodyFont="Inter"
>
{children}
</ThemeProvider>
);
}Prefer no wrapper? Override the semantic variables directly — the components pick them up the same way:
:root {
--accent: 70 167 88; /* r g b — brand accent */
--radius-lg: 0.5rem;
--radius-panel: 0.75rem;
}
.dark {
--accent: 86 202 106; /* lighter accent for dark mode */
}Foundation and semantic tokens are documented under Colors, Radius, and Shadows. Applying a preset name resolves to these same variables via applyAccentColor / applyGrayColor / applyThemePreset (exported for programmatic use).