Theming & Styling
SvelteForge Admin uses Tailwind CSS 4 with a native CSS theming system — there is no JavaScript configuration file. All colors are defined using the OKLCH color space, mapped to semantic design tokens, and consumed by both Tailwind utility classes and shadcn-svelte components. Dark mode is handled by mode-watcher and integrates seamlessly with Svelte 5's runes API.
Tailwind CSS 4 Setup
Tailwind CSS 4 is a ground-up rewrite that moves configuration into native CSS. SvelteForge
Admin's setup lives entirely in src/app.css:
@import "tailwindcss";
@import "tw-animate-css";
@source "../node_modules/layerchart";
@source "../node_modules/svelte-ux";
@custom-variant dark (&:is(.dark *)); What each line does
@import "tailwindcss"— Loads the Tailwind CSS 4 framework@import "tw-animate-css"— Adds animation utilities (fade, slide, zoom, etc.)@sourcedirectives — Tell Tailwind to scan LayerChart and svelte-ux packages for class names so they are included in the generated CSS@custom-variant dark— Defines the dark mode variant as class-based (.darkon an ancestor element) rather than theprefers-color-schememedia query
Vite plugin
On the build side, SvelteForge uses @tailwindcss/vite as a Vite plugin. This replaces
the PostCSS-based setup from Tailwind v3 and provides faster compilation integrated directly into
the SvelteKit dev server.
OKLCH Color System
All colors in SvelteForge Admin are defined in the OKLCH color space rather than the traditional HSL. OKLCH stands for:
- L — Lightness (0 = black, 1 = white)
- C — Chroma (0 = gray, higher = more saturated)
- H — Hue angle (0-360, like a color wheel)
Why OKLCH over HSL?
OKLCH is perceptually uniform — two colors with the same lightness value actually look equally bright to the human eye. In HSL, a yellow at 50% lightness looks far brighter than a blue at 50% lightness. OKLCH corrects this, making it much easier to create harmonious color palettes and maintain consistent contrast ratios across your entire theme.
It also makes customization intuitive: to make a color lighter, increase L. To make it more vibrant, increase C. To shift the hue, change H. Each axis is independent.
Color Tokens
Colors are defined as CSS custom properties in :root (light mode) and .dark (dark mode). Every component in the system references these tokens, so changing a
single variable updates the entire UI.
Light mode (:root)
| Token | OKLCH Value | Usage |
|---|---|---|
--background | oklch(1 0 0) | Page background (pure white) |
--foreground | oklch(0.141 0.005 285.823) | Primary text color (near black) |
--card | oklch(1 0 0) | Card backgrounds |
--card-foreground | oklch(0.141 0.005 285.823) | Card text |
--popover | oklch(1 0 0) | Popover/dropdown backgrounds |
--popover-foreground | oklch(0.141 0.005 285.823) | Popover text |
--primary | oklch(0.21 0.006 285.885) | Primary actions, active states |
--primary-foreground | oklch(0.985 0 0) | Text on primary backgrounds |
--secondary | oklch(0.967 0.001 286.375) | Secondary actions, subtle backgrounds |
--secondary-foreground | oklch(0.21 0.006 285.885) | Text on secondary backgrounds |
--muted | oklch(0.967 0.001 286.375) | Muted backgrounds, disabled states |
--muted-foreground | oklch(0.552 0.016 285.938) | Secondary text, placeholders |
--accent | oklch(0.967 0.001 286.375) | Hover/focus highlights |
--accent-foreground | oklch(0.21 0.006 285.885) | Text on accent backgrounds |
--destructive | oklch(0.577 0.245 27.325) | Delete buttons, error states |
--destructive-foreground | oklch(0.985 0 0) | Text on destructive backgrounds |
--border | oklch(0.92 0.004 286.32) | All borders globally |
--input | oklch(0.92 0.004 286.32) | Form input borders |
--ring | oklch(0.705 0.015 286.067) | Focus ring color |
Chart colors
| Token | Light | Dark |
|---|---|---|
--chart-1 | oklch(0.585 0.233 277.117) | oklch(0.673 0.245 277.117) |
--chart-2 | oklch(0.685 0.169 237.323) | oklch(0.746 0.160 237.323) |
--chart-3 | oklch(0.606 0.250 292.717) | oklch(0.700 0.242 292.717) |
--chart-4 | oklch(0.704 0.140 181.071) | oklch(0.765 0.132 181.071) |
--chart-5 | oklch(0.696 0.170 162.480) | oklch(0.765 0.160 162.480) |
Notice how the dark mode chart colors have higher lightness values — this ensures the charts remain legible against the dark background.
Sidebar colors
| Token | Light | Dark |
|---|---|---|
--sidebar | oklch(0.985 0 0) | oklch(0.21 0.006 285.885) |
--sidebar-foreground | oklch(0.141 0.005 285.823) | oklch(0.985 0 0) |
--sidebar-primary | oklch(0.21 0.006 285.885) | oklch(0.488 0.243 264.376) |
--sidebar-primary-foreground | oklch(0.985 0 0) | oklch(0.985 0 0) |
--sidebar-accent | oklch(0.967 0.001 286.375) | oklch(0.274 0.006 286.033) |
--sidebar-accent-foreground | oklch(0.21 0.006 285.885) | oklch(0.985 0 0) |
--sidebar-border | oklch(0.92 0.004 286.32) | oklch(1 0 0 / 10%) |
--sidebar-ring | oklch(0.705 0.015 286.067) | oklch(0.552 0.016 285.938) |
The sidebar has its own color tokens so it can have a slightly different background or accent color from the main content area — useful for visually distinguishing navigation from content.
The @theme inline Block
Tailwind CSS 4 uses a @theme directive to map CSS custom properties to Tailwind
utility classes. SvelteForge Admin defines this in src/app.css:
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
/* ... all other semantic tokens ... */
} How it works
The @theme inline block tells Tailwind to use these CSS variables as its color and
spacing values. The --color- prefix is stripped when generating utility classes, so --color-primary becomes bg-primary, text-primary, border-primary, etc.
Radius system
A single --radius variable (0.625rem) drives all border radius values through calculated
variants:
--radius-sm—calc(var(--radius) - 4px)— Small elements like badges--radius-md—calc(var(--radius) - 2px)— Inputs, buttons--radius-lg—var(--radius)— Cards, dialogs--radius-xl—calc(var(--radius) + 4px)— Large containers
Change --radius once and every component in the system updates proportionally.
Dark Mode Implementation
SvelteForge Admin uses class-based dark mode powered by the mode-watcher library. Here is how all the pieces fit together in the SvelteKit application:
How it works
- mode-watcher manages the theme state and persists the user's preference to
localStorage - It adds or removes the
.darkclass on the<html>element - Tailwind's
@custom-variant dark (&:is(.dark *))directive makes alldark:utilities activate when any ancestor has the.darkclass - The CSS custom properties in the
.darkselector override the:rootvalues, so every semantic color token flips automatically
System preference detection
On first visit, mode-watcher checks the user's operating system preference via prefers-color-scheme. If the user has their OS set to dark mode, the app starts in
dark mode. Once the user explicitly toggles the theme, their choice is saved and takes precedence
over the system setting.
Svelte 5 integration
In Svelte 5, mode-watcher exposes a runes object rather than a Svelte store. This is a critical distinction:
<script lang="ts">
import { mode, toggleMode } from "mode-watcher";
// CORRECT — Svelte 5 runes object
let isDark = $derived(mode.current === "dark");
// WRONG — This is Svelte 4 store syntax, do NOT use
// let isDark = $mode === "dark";
</script>
<button onclick={toggleMode}>
{isDark ? "Switch to light" : "Switch to dark"}
</button> Use mode.current to read the theme and toggleMode() to switch it. This pattern
is used in the Theme Toggle component and the Command Palette's quick actions.
Customizing Colors
To customize the color scheme, edit the CSS custom properties in src/app.css. Because
all colors use OKLCH, you can adjust each axis independently:
Example: changing the primary color to blue
:root {
/* Original (dark gray) */
/* --primary: oklch(0.21 0.006 285.885); */
/* Custom blue primary */
--primary: oklch(0.45 0.2 260);
--primary-foreground: oklch(0.985 0 0);
}
.dark {
/* Lighter blue for dark mode */
--primary: oklch(0.65 0.2 260);
--primary-foreground: oklch(0.15 0.005 260);
} Every button, link, active state, sidebar highlight, and badge that uses bg-primary or text-primary will immediately reflect the change — no component edits required.
OKLCH tips for customization
- Keep L (lightness) between 0.3-0.5 for primary colors in light mode, and 0.6-0.8 for dark mode
- Keep C (chroma) moderate (0.1-0.2) for professional palettes, higher (0.2+) for vibrant ones
- The H (hue) axis maps to: 0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 300=purple
shadcn-svelte Theming
All shadcn-svelte components use the semantic color tokens defined above. They reference classes
like bg-primary, text-muted-foreground, border-border, and bg-card — so they adapt to your theme automatically.
When you add a new shadcn-svelte component:
npx shadcn-svelte@latest add <component-name> It will use the same tokens and immediately match your custom color scheme. No per-component theme configuration is needed.
Typography
SvelteForge Admin uses the system font stack via Tailwind's default font-sans — this means the app uses the native font on each platform (SF Pro on macOS,
Segoe UI on Windows, Roboto on Android) for the fastest possible text rendering with no web font downloads.
Documentation pages (like this one) use Tailwind Typography (prose)
classes applied in the docs layout for readable long-form content with proper heading hierarchy,
paragraph spacing, code block styling, and table formatting.
Responsive Design
SvelteForge Admin follows a mobile-first approach. Base styles target small screens, and responsive breakpoints add complexity for larger viewports.
Collapsible sidebar
The main navigation sidebar uses the IsMobile hook (a Svelte 5 runes-based class at src/lib/hooks/is-mobile.svelte.ts) to detect the 768px
breakpoint. On mobile viewports, the sidebar collapses into a slide-out overlay with a hamburger
toggle. On desktop, it remains visible as a fixed sidebar.
Grid layouts
Dashboard cards and analytics grids use Tailwind's responsive grid utilities to stack vertically on mobile and expand to multi-column layouts on larger screens:
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<!-- Stat cards stack on mobile, 2-up on tablet, 4-up on desktop -->
</div> Base Layer Styles
SvelteForge Admin applies two global base styles via Tailwind's @layer base directive:
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
} - Global border color — Every element's border defaults to the
--bordertoken. This means you can addborderto any element and it picks up the theme-appropriate border color automatically. - Body defaults — The page background and text color are set from the semantic tokens, ensuring the entire app responds to theme changes from a single source of truth.
Want Advanced Theming?
Live Theme Customizer on DashboardPack
Want a live theme customizer with 6 color presets, 3 density levels, and RTL support? Our premium templates include it built-in — users can switch themes, adjust spacing, and toggle layout direction in real time without touching CSS.
- 6 color presets — Switch entire palettes with one click
- 3 density levels — Compact, default, and comfortable spacing
- RTL support — Full right-to-left layout mirroring
- Persistent preferences — Saved per-user, not just per-session