Skeleton v3 + Tailwind CSS 4 + Svelte 5. The modern stack for rapid, beautiful UI development.
🚀 Quick Setup
1. Install Dependencies
npm install -D @skeletonlabs/skeleton @skeletonlabs/skeleton-svelte tailwindcss @tailwindcss/vite2. Configure Tailwind 4
/* app.css */
@import "tailwindcss";
@import "@skeletonlabs/skeleton";
@import "@skeletonlabs/skeleton-svelte";
@import "@skeletonlabs/skeleton/themes/cerberus";
@plugin '@tailwindcss/forms';
@plugin '@tailwindcss/typography';3. Vite Config
// vite.config.ts
import { sveltekit } from "@sveltejs/kit/vite"
import { defineConfig } from "vite"
import tailwindcss from "@tailwindcss/vite"
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
})4. HTML Theme
<!-- app.html -->
<html lang="en" data-theme="cerberus"></html>🎨 Theme System
Built-in Themes
| Theme | Vibe | Best For |
|---|---|---|
cerberus | Professional, clean | SaaS, dashboards |
catppuccin | Soft, pastel | Creative apps |
vintage | Warm, retro | Niche products |
wintry | Cool, crisp | Winter themes |
modern | Neutral, flexible | General use |
Custom Theme
/* app.css */
[data-theme="custom"] {
--color-primary-50: #f0f9ff;
--color-primary-500: #0ea5e9;
--color-primary-950: #082f49;
--color-surface-50: #ffffff;
--color-surface-100: #f8fafc;
--color-surface-900: #0f172a;
--radius-base: 0.5rem;
--radius-container: 1rem;
}🧩 Component Patterns
Buttons
<script>
import { Button } from '@skeletonlabs/skeleton-svelte'
</script>
<!-- Presets -->
<Button preset="filled">Filled</Button>
<Button preset="tonal">Tonal</Button>
<Button preset="outlined">Outlined</Button>
<Button preset="ghost">Ghost</Button>
<!-- With Icons -->
<Button preset="filled">
<PlusIcon class="size-4 mr-2" />
Add Item
</Button>
<!-- Sizes -->
<Button preset="filled" classes="btn-sm">Small</Button>
<Button preset="filled">Default</Button>
<Button preset="filled" classes="btn-lg">Large</Button>
<!-- States -->
<Button preset="filled" disabled>Disabled</Button>
<Button preset="filled" loading>Loading</Button>Direct classes (no component import):
<button class="btn preset-filled">Button</button>
<button class="btn preset-tonal-primary">Primary Tonal</button>
<button class="btn preset-outlined-error">Error Outlined</button>Cards
<!-- Basic Card -->
<div class="card preset-filled-surface-100-900 p-4">
<h3 class="h3">Card Title</h3>
<p class="opacity-60">Card content here</p>
</div>
<!-- Outlined Card -->
<div class="card preset-outlined-surface-200-800 p-4">
Content
</div>
<!-- Interactive Card -->
<div class="card preset-filled-surface-100-900
hover:preset-tonal
cursor-pointer
transition-all">
Hover me
</div>
<!-- Header + Content -->
<div class="card">
<header class="card-header flex justify-between items-center">
<h4 class="h4">Settings</h4>
<button class="btn-icon preset-tonal">
<XIcon class="size-4" />
</button>
</header>
<section class="p-4">
Content
</section>
<footer class="card-footer flex justify-end gap-2">
<button class="btn preset-tonal">Cancel</button>
<button class="btn preset-filled-primary">Save</button>
</footer>
</div>Forms
<script>
import { Segment, Switch } from '@skeletonlabs/skeleton-svelte'
</script>
<!-- Input with label -->
<label class="label">
<span class="label-text">Email</span>
<input
type="email"
class="input"
placeholder="you@example.com"
/>
</label>
<!-- Floating label -->
<label class="floating-label">
<input type="text" class="input" placeholder=" " />
<span>Username</span>
</label>
<!-- Select -->
<select class="select">
<option value="">Choose...</option>
<option value="1">Option 1</option>
</select>
<!-- Switch -->
<Switch name="notifications" checked={enabled}>
{#snippet children()}Enable notifications{/snippet}
</Switch>
<!-- Segment (Radio group) -->
<Segment name="plan" value={selectedPlan}>
{#snippet children()}
<Segment.Item value="free">Free</Segment.Item>
<Segment.Item value="pro">Pro</Segment.Item>
<Segment.Item value="enterprise">Enterprise</Segment.Item>
{/snippet}
</Segment>
<!-- Textarea -->
<textarea class="textarea" rows="4" placeholder="Description..."></textarea>Modals & Overlays
<script>
import { Modal } from '@skeletonlabs/skeleton-svelte'
let open = $state(false)
</script>
<button class="btn preset-filled" onclick={() => open = true}>
Open Modal
</button>
<Modal
bind:open
triggerBase="btn preset-tonal"
contentBase="card preset-filled-surface-100-900 p-4 w-full max-w-md"
backdropClasses="backdrop-blur-sm"
>
{#snippet trigger()}Open{/snippet}
{#snippet content()}
<header class="flex justify-between items-center mb-4">
<h3 class="h3">Confirm Action</h3>
<button class="btn-icon preset-tonal" onclick={() => open = false}>
<XIcon class="size-4" />
</button>
</header>
<p class="mb-4">Are you sure you want to proceed?</p>
<footer class="flex justify-end gap-2">
<button class="btn preset-tonal" onclick={() => open = false}>
Cancel
</button>
<button class="btn preset-filled-error" onclick={confirm}>
Delete
</button>
</footer>
{/snippet}
</Modal>Navigation
<script>
import { AppBar, AppShell, Navigation } from '@skeletonlabs/skeleton-svelte'
</script>
<!-- App Shell -->
<AppShell>
{#snippet header()}
<AppBar>
{#snippet lead()}
<h1 class="h4 font-bold">My App</h1>
{/snippet}
{#snippet trail()}
<button class="btn-icon preset-tonal">
<UserIcon class="size-5" />
</button>
{/snippet}
</AppBar>
{/snippet}
{#snippet sidebarLeft()}
<Navigation.Rail>
<Navigation.Tile label="Home" href="/">
<HomeIcon class="size-6" />
</Navigation.Tile>
<Navigation.Tile label="Settings" href="/settings">
<SettingsIcon class="size-6" />
</Navigation.Tile>
</Navigation.Rail>
{/snippet}
{#snippet page()}
<main class="p-4">
<slot />
</main>
{/snippet}
</AppShell>Data Display
<script>
import { Table, Paginator } from '@skeletonlabs/skeleton-svelte'
</script>
<!-- Table -->
<Table
data={users}
columns={[
{ field: 'name', header: 'Name' },
{ field: 'email', header: 'Email' },
{
field: 'status',
header: 'Status',
render: (row) => row.status === 'active'
? '<span class="badge preset-filled-success">Active</span>'
: '<span class="badge preset-filled-error">Inactive</span>'
}
]}
/>
<!-- Badges -->
<span class="badge preset-filled-primary">New</span>
<span class="badge preset-tonal-secondary">Draft</span>
<span class="badge preset-outlined-warning">Pending</span>
<!-- Avatar -->
<Avatar
src="/avatar.jpg"
name="John Doe"
classes="size-10"
/>
<Avatar
initials="JD"
classes="preset-filled-primary"
/>
<!-- Progress -->
<Progress
value={60}
max={100}
meter="bg-primary-500"
track="bg-surface-200-800"
/>🎨 Utility Classes
Color Shortcuts
<!-- Background -->
<div class="preset-filled-primary">Primary fill</div>
<div class="preset-tonal-secondary">Secondary tonal</div>
<div class="preset-outlined-tertiary">Tertiary outlined</div>
<!-- Text -->
<p class="text-primary-500">Primary text</p>
<p class="text-surface-600-400">Adaptive text</p>
<!-- Border -->
<div class="border-primary-500">Primary border</div>
<div class="border-surface-200-800">Adaptive border</div>Spacing Tokens
1 = 0.25rem (4px)
2 = 0.5rem (8px)
3 = 0.75rem (12px)
4 = 1rem (16px)
5 = 1.25rem (20px)
6 = 1.5rem (24px)
8 = 2rem (32px)
10 = 2.5rem (40px)
12 = 3rem (48px)
Typography Scale
<h1 class="h1">Heading 1</h1>
<h2 class="h2">Heading 2</h2>
<h3 class="h3">Heading 3</h3>
<h4 class="h4">Heading 4</h4>
<p class="text-base">Body text</p>
<p class="text-sm">Small text</p>
<p class="text-xs">Extra small</p>🌓 Dark Mode
<script>
import { mode, toggleMode } from '@skeletonlabs/skeleton-svelte'
</script>
<!-- Current mode indicator -->
<p>Current: {$mode}</p>
<!-- Toggle button -->
<button
class="btn preset-tonal"
onclick={toggleMode}
>
{#if $mode === 'light'}
<MoonIcon class="size-4" />
{:else}
<SunIcon class="size-4" />
{/if}
</button>
<!-- Manual theme class -->
<div class="dark">
<!-- Always dark in this container -->
</div>🔥 Common Patterns
Toast Notifications
<script>
import { Toast, createToaster } from '@skeletonlabs/skeleton-svelte'
const toaster = createToaster()
function showToast() {
toaster.success({
title: 'Success!',
description: 'Your changes have been saved.'
})
}
</script>
<button class="btn preset-filled" onclick={showToast}>
Show Toast
</button>
<Toast.Group {toaster} />Tabs
<script>
import { Tabs } from '@skeletonlabs/skeleton-svelte'
let active = $state('account')
</script>
<Tabs value={active} onValueChange={(e) => active = e.value}>
<Tabs.List classes="bg-surface-100-900/50 p-1 rounded-lg">
<Tabs.Trigger value="account" classes="px-4 py-2 rounded-md">
Account
</Tabs.Trigger>
<Tabs.Trigger value="settings" classes="px-4 py-2 rounded-md">
Settings
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="account">
Account content
</Tabs.Content>
<Tabs.Content value="settings">
Settings content
</Tabs.Content>
</Tabs>Accordion
<script>
import { Accordion } from '@skeletonlabs/skeleton-svelte'
</script>
<Accordion>
<Accordion.Item>
{#snippet summary()}Section 1{/snippet}
{#snippet content()}Content for section 1{/snippet}
</Accordion.Item>
<Accordion.Item>
{#snippet summary()}Section 2{/snippet}
{#snippet content()}Content for section 2{/snippet}
</Accordion.Item>
</Accordion>🎯 Best Practices
- Use
preset-*classes — consistent with theme system - Prefer semantic colors —
primary,secondary,error, not hex codes - Use adaptive colors —
surface-100-900works in both modes - Combine with Tailwind —
card preset-filled p-4 shadow-lg - Leverage snippets — cleaner than slots in Svelte 5