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.)
  • @source directives — 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 (.dark on an ancestor element) rather than the prefers-color-scheme media 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)

TokenOKLCH ValueUsage
--backgroundoklch(1 0 0)Page background (pure white)
--foregroundoklch(0.141 0.005 285.823)Primary text color (near black)
--cardoklch(1 0 0)Card backgrounds
--card-foregroundoklch(0.141 0.005 285.823)Card text
--popoveroklch(1 0 0)Popover/dropdown backgrounds
--popover-foregroundoklch(0.141 0.005 285.823)Popover text
--primaryoklch(0.21 0.006 285.885)Primary actions, active states
--primary-foregroundoklch(0.985 0 0)Text on primary backgrounds
--secondaryoklch(0.967 0.001 286.375)Secondary actions, subtle backgrounds
--secondary-foregroundoklch(0.21 0.006 285.885)Text on secondary backgrounds
--mutedoklch(0.967 0.001 286.375)Muted backgrounds, disabled states
--muted-foregroundoklch(0.552 0.016 285.938)Secondary text, placeholders
--accentoklch(0.967 0.001 286.375)Hover/focus highlights
--accent-foregroundoklch(0.21 0.006 285.885)Text on accent backgrounds
--destructiveoklch(0.577 0.245 27.325)Delete buttons, error states
--destructive-foregroundoklch(0.985 0 0)Text on destructive backgrounds
--borderoklch(0.92 0.004 286.32)All borders globally
--inputoklch(0.92 0.004 286.32)Form input borders
--ringoklch(0.705 0.015 286.067)Focus ring color

Chart colors

TokenLightDark
--chart-1oklch(0.585 0.233 277.117)oklch(0.673 0.245 277.117)
--chart-2oklch(0.685 0.169 237.323)oklch(0.746 0.160 237.323)
--chart-3oklch(0.606 0.250 292.717)oklch(0.700 0.242 292.717)
--chart-4oklch(0.704 0.140 181.071)oklch(0.765 0.132 181.071)
--chart-5oklch(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

TokenLightDark
--sidebaroklch(0.985 0 0)oklch(0.21 0.006 285.885)
--sidebar-foregroundoklch(0.141 0.005 285.823)oklch(0.985 0 0)
--sidebar-primaryoklch(0.21 0.006 285.885)oklch(0.488 0.243 264.376)
--sidebar-primary-foregroundoklch(0.985 0 0)oklch(0.985 0 0)
--sidebar-accentoklch(0.967 0.001 286.375)oklch(0.274 0.006 286.033)
--sidebar-accent-foregroundoklch(0.21 0.006 285.885)oklch(0.985 0 0)
--sidebar-borderoklch(0.92 0.004 286.32)oklch(1 0 0 / 10%)
--sidebar-ringoklch(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

  1. mode-watcher manages the theme state and persists the user's preference to localStorage
  2. It adds or removes the .dark class on the <html> element
  3. Tailwind's @custom-variant dark (&:is(.dark *)) directive makes all dark: utilities activate when any ancestor has the .dark class
  4. The CSS custom properties in the .dark selector override the :root values, 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 --border token. This means you can add border to 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