Getting Started

Theming

Svelora uses an OKLCH color system with CSS custom properties. Every color automatically adapts to light and dark mode.

Import Theme

Import `svelora/theme.css` in your layout CSS to load all color tokens.

`src/routes/layout.css`

@import 'tailwindcss';
@import 'svelora/theme.css';

@custom-variant dark (&:where(.dark, .dark *));

OKLCH Color Space

Svelora uses OKLCH, a perceptually uniform color space. Colors stay more consistent across lightness levels.

/* oklch(lightness chroma hue) */

/* Lightness: 0 (black) -> 1 (white) */
/* Chroma: 0 (gray) -> 0.4 (vivid) */
/* Hue: 0-360 (color wheel angle) */

--color-primary: oklch(0.546 0.245 262.88);
/* bright vivid blue */

Color Tokens

Each semantic color provides base, on-color, container, and on-container tokens.

primary
primary-container
secondary
secondary-container
tertiary
tertiary-container
success
success-container
warning
warning-container
error
error-container
info
info-container
surface
surface-container

Each color provides `--color-{name}`, `--color-on-{name}`, `--color-{name}-container`, and `--color-on-{name}-container`.

Surface System

Surface tokens provide depth-based backgrounds for pages, cards, dialogs, drawers, and popovers.

`bg-surface`
`bg-surface-container-low`
`bg-surface-container`
`bg-surface-container-high`
`bg-surface-container-highest`

Borders & text

`text-on-surface-variant`

`border-outline`

Using in Tailwind

<!-- Background colors -->
<div class="bg-primary">Primary background</div>
<div class="bg-surface-container">Elevated surface</div>

<!-- Text colors -->
<p class="text-on-surface">Primary text</p>
<p class="text-on-surface/60">Muted text (60% opacity)</p>

<!-- Border colors -->
<div class="border-outline-variant">Subtle border</div>

<!-- Combined -->
<div class="bg-primary-container text-on-primary-container">
 Tinted container
</div>

Customizing Colors

Override CSS custom properties to customize any color. Define both light and dark values.

`src/routes/layout.css`

@import 'tailwindcss';
@import 'svelora/theme.css';

@custom-variant dark (&:where(.dark, .dark *));

:root {
 --color-primary: oklch(0.60 0.20 280);
 --color-on-primary: oklch(1 0 0);
 --color-primary-container: oklch(0.90 0.05 280);
 --color-on-primary-container: oklch(0.25 0.10 280);
}

.dark {
 --color-primary: oklch(0.75 0.15 280);
 --color-on-primary: oklch(0.20 0.05 280);
 --color-primary-container: oklch(0.30 0.08 280);
 --color-on-primary-container: oklch(0.90 0.05 280);
}

Always override both `:root` and `.dark` values to preserve contrast in every theme.

Border Radius

:root {
 --radius-xs: 0.125rem;
 --radius-sm: 0.25rem;
 --radius-md: 0.375rem;
 --radius-lg: 0.5rem;
 --radius-xl: 0.75rem;
 --radius-2xl: 1rem;
 --radius-3xl: 1.5rem;
 --radius-full: 9999px;
}

Fonts Provider

Use the `Fonts` provider to load either Google Fonts or local font files and map them to Svelora variables such as `--font-sans-family`, `--font-heading-family`, and `--font-mono-family`.

Poppins heading

Inter body copy is applied through `--font-sans-family`.

const family = 'font-mono';

`src/routes/+layout.svelte`

<script lang="ts">
 import { Fonts } from 'svelora';
 import { ModeWatcher } from 'mode-watcher';
 import 'svelora/theme.css';

 let { children } = $props();
</script>

<Fonts
 families={[
  { name: 'Inter', variable: '--font-sans-family', weights: [400, 500, 600, 700] },
  { name: 'Poppins', variable: '--font-heading-family', weights: [600, 700] },
  { name: 'JetBrains Mono', variable: '--font-mono-family', weights: [400, 500, 700] }
 ]}
/>

<ModeWatcher />
{@render children?.()}

`src/svelora.config.ts`

import { defineConfig } from 'svelora';

defineConfig({
 fonts: {
  families: [
   { name: 'Inter', variable: '--font-sans-family', weights: [400, 500, 600, 700] },
   { name: 'Poppins', variable: '--font-heading-family', weights: [600, 700] },
   { name: 'JetBrains Mono', variable: '--font-mono-family', weights: [400, 500, 700] },
   {
    provider: 'local',
    name: 'Sarabun',
    variable: '--font-sarabun-family',
    sources: [{ src: '/fonts/Sarabun-Regular.woff2', format: 'woff2', weight: 400 }]
   }
  ]
 }
});

Top-level config uses `fonts`, `fonts: false` disables defaults, `font-heading` is available as a utility class, and `font-mono` automatically uses `--font-mono-family`.

Custom class: `font-sarabun`

<script lang="ts">
 import { Fonts } from 'svelora';
</script>

<Fonts
 families={[
  {
   provider: 'local',
   name: 'Sarabun',
   variable: '--font-sarabun-family',
   sources: [{ src: '/fonts/Sarabun-Regular.woff2', format: 'woff2', weight: 400 }]
  }
 ]}
/>

<p class="font-sarabun">Hello Sarabun</p>

Svelora now exposes `font-sarabun` through theme tokens, so you can use it directly in classes across the project after mapping `--font-sarabun-family`.

Mode Watcher (Dark Mode)

Svelora uses mode-watcher to handle dark mode seamlessly without a flash of unstyled content (FOUC). You must install it as a peer dependency: npm install mode-watcher

Place the <ModeWatcher /> component at the root of your app (in +layout.svelte). You can customize its behavior using props:

<!-- Default behavior (syncs with system, saves to localStorage) -->
<ModeWatcher />

<!-- Force dark mode by default -->
<ModeWatcher defaultMode="dark" />

<!-- Disable saving to localStorage (always use system preference) -->
<ModeWatcher track={false} />

You can also import mode and toggleMode directly from mode-watcher to build your own theme toggle buttons.

Global Configuration

Use `defineConfig` to set library-wide defaults for variants, slots, and icons.

`src/lib/config.ts`

import { defineConfig } from 'svelora';

defineConfig({
 button: {
  defaultVariants: { variant: 'outline', size: 'md' },
  slots: { base: 'rounded-full' }
 },
 avatar: {
  defaultVariants: { size: 'lg' },
  slots: { root: 'ring-2 ring-primary' }
 },
 icons: {
  loading: 'svg-spinners:ring-resize',
  close: 'lucide:x',
  chevronDown: 'lucide:chevron-down',
  chevronRight: 'lucide:chevron-right',
  check: 'lucide:check',
  light: 'lucide:sun',
  dark: 'lucide:moon'
 }
});

Per-Component Overrides

Every component accepts a `ui` prop to override specific slot classes on individual instances.

<Button
 ui={{ base: 'rounded-full shadow-lg', label: 'uppercase' }}
 label="Custom Button"
/>

<Card ui={{ root: 'shadow-xl', header: 'bg-primary/5' }}>
 {#snippet header()}
 <h3>Styled Card</h3>
 {/snippet}
 Content here
</Card>
Soft