Theme System
Shavin UI ships with 24 distinct accent color palettes, 5 grayscales, concentric corner radii, and sizing scale factors. Configure theme parameters globally via ThemeProvider at your application root or declare CSS custom properties directly.
Declare a theme in code
Mount once at the app root.
import { ThemeProvider } from "@shavin/ui";
export default function App() {
return (
<ThemeProvider
accentColor="grass"
grayColor="zinc"
radius="large"
scaling="95%"
panelBackground="translucent"
>
<MainLayout />
</ThemeProvider>
);
}Override variables directly — no wrapper needed.
:root {
--accent: 70 167 88;
--radius-lg: 0.5rem;
--radius-panel: 0.75rem;
}
.dark {
--accent: 86 202 106;
}Theme presets
16 curated themesSkip manual property declarations — pass a single theme name to load a curated brand config including accent, neutrals, radius, density, panel style, and a display/body font pairing. Add individual props alongside to override any setting.
import { ThemeProvider } from "@shavin/ui";
<ThemeProvider theme="Editorial">
<App />
</ThemeProvider>Font pairings load dynamically from Google Fonts. Tweak any control in the Configurator (top-right) and the preset auto-switches to “Custom”.
Scoped Theming
<ScopedThemeProvider>Wrap any subtree in <ScopedThemeProvider> to isolate theme settings (accent color, gray scale, radius, scaling, appearance) without altering the global application :root.
import { ScopedThemeProvider, Button, Card, Badge, Text } from "@shavin/ui";
export function ScopedIsland() {
return (
<ScopedThemeProvider
accentColor="grass"
grayColor="zinc"
radius="large"
appearance="dark"
className="p-6 rounded-[var(--radius-panel)] border border-hairline bg-canvas min-h-[320px]"
>
<Card padding="md" className="flex flex-col gap-3">
<Badge color="accent">Scoped Theme</Badge>
<Text weight="medium">Tokens inside this container are fully isolated.</Text>
<Button variant="solid" tone="accent">Accent Action</Button>
</Card>
</ScopedThemeProvider>
);
}Smart Accent Token System
Switching to a high-luminance accent (Lime, Mint, Amber) breaks text readability in traditional libraries. @shavin/ui computes a full cluster of WCAG AAA-accessible tokens from relative luminance — automatically, for all 24 presets in both themes.
BlueBlueEvaluates exact linear RGB coefficients per WCAG spec:L = 0.2126R + 0.7152G + 0.0722B
Accents with L > 0.35 in light mode are dynamically darkened for --accent-contrast text, while dark accents in dark mode are complemented to ensure crisp ≥ 4.5:1 text contrast.
Monochrome black (0 0 0) seamlessly inverts to pure white (255 255 255) in dark mode, maintaining pristine brand contrast without manual theme configuration.
Surface Contrast Scoping
Layout primitives accept a surface prop that creates a local contrast scope — semantic tokens remap independently of the page theme. Switch between the four modes below to see how each behaves on light vs dark pages.
| Prop | Light page | Dark page | Use case |
|---|---|---|---|
dark | dark tokens | inverts → light | Contrast band against page |
light | light tokens | inverts → dark | Contrast band against page |
dark-always | dark tokens | dark tokens | Dark media bg — text stays light |
light-always | light tokens | light tokens | Light media bg — text stays dark |
surface="dark-always"Section title
Content inside the surface scope.
surface="dark-always"Section title
Content inside the surface scope.
<Flex direction="column" gap="3" surface="dark-always"
bgImage="https://images.unsplash.com/photo-1719841602002-1dcc795bed24"
bgOverlays="dark-50"
className="rounded-[var(--radius-panel)] p-6">
<Heading size="sm">Section title</Heading>
<Text size="xs" color="subtle">Content inside the surface scope.</Text>
<Flex direction="row" gap="2">
<Button size="sm" variant="solid" tone="accent">Action</Button>
<Button size="sm" variant="outline">Cancel</Button>
</Flex>
</Flex>