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

Recommended

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

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 themes

Skip 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>
dark
dark
dark
dark
dark
dark
dark
dark
dark
light
light
light
light
light
light
light

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.

Live Component Preview
lightBlue
Badgecontrast text
darkBlue
Badgecontrast text
1. Mathematical Luminance

Evaluates exact linear RGB coefficients per WCAG spec:L = 0.2126R + 0.7152G + 0.0722B

2. Adaptive Contrast Text

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.

3. Achromatic Inversion

Monochrome black (0 0 0) seamlessly inverts to pure white (255 255 255) in dark mode, maintaining pristine brand contrast without manual theme configuration.

Complete Accent Semantic Hierarchy
--accentSolid brand fill for primary buttons, active toggles, and key accents.
--accent-fgContrast-safe foreground (black or white) rendered on top of solid accent fills.
--accent-contrastLuminance-corrected accent tone for text, icons, and glyphs on canvas/surface.
--accent-subtle12% translucent background tint for soft-variant buttons and tags.

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.

PropLight pageDark pageUse case
darkdark tokensinverts → lightContrast band against page
lightlight tokensinverts → darkContrast band against page
dark-alwaysdark tokensdark tokensDark media bg — text stays light
light-alwayslight tokenslight tokensLight media bg — text stays dark
light pagesurface="dark-always"

Section title

Content inside the surface scope.

dark pagesurface="dark-always"

Section title

Content inside the surface scope.

surface prop — live preview
<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>
Configure Theme
Theme
Accent Color
Custom
Gray Family
Appearance
Radius
Scaling
Panel Style
Heading Font
Body Font