Installation
Install Monsoon in Svelte with Vite
Svelte gives you two tidy tools that map straight onto Monsoon: the class: directive for conditional classes and the style: directive for the seed color. Monsoon itself is one import in src/main.js.
Install
Start from npm create vite@latest my-app -- --template svelte. Building a full app with routing? Use the SvelteKit guide.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My app</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/monsooncss@0.2.0/dist/monsoon.min.css" />
<script>(function(){var t;try{t=localStorage.getItem('monsoon-theme')}catch(e){}var d=t==='dark'||(t!=='light'&&matchMedia('(prefers-color-scheme: dark)').matches);document.documentElement.classList.add(d?'dark':'light')})()</script>
</head>
<body class="bg-background text-foreground antialiased">
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>npm install monsooncssimport { mount } from 'svelte';
import 'monsooncss';
import './app.css';
import App from './App.svelte';
const app = mount(App, { target: document.getElementById('app') });
export default app;Keep the head script from the CDN tab in index.html, and clear the demo rules in src/app.css.
Use the classes
Static names go in class, and each class:name={condition} directive adds one class when the condition is true:
<script>
let { name, price, featured = false } = $props();
</script>
<article
class="rounded-2xl border bg-surface p-6 space-y-4"
class:shadow-sm={!featured}
class:ring-2={featured}
class:ring-ring={featured}
>
<h2 class="text-lg font-semibold">{name}</h2>
<p class="text-3xl font-bold">{price}</p>
<button
type="button"
class={featured
? 'w-full rounded-lg px-4 py-2 font-medium bg-primary text-primary-foreground hover:bg-primary/90'
: 'w-full rounded-lg px-4 py-2 font-medium bg-secondary text-secondary-foreground hover:bg-secondary/80'}
>
Choose {name}
</button>
</article><script>
import PlanCard from './lib/PlanCard.svelte';
</script>
<main class="mx-auto max-w-5xl px-6 py-12">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<PlanCard name="Starter" price="$0" />
<PlanCard name="Team" price="$12" featured />
<PlanCard name="Business" price="$40" />
</div>
</main>Svelte 5.16 and newer also accept objects and arrays in class, for example class={['rounded-lg px-4 py-2', { 'bg-primary': active }]}.
Dark mode
<script>
let isDark = $state(document.documentElement.classList.contains('dark'));
function toggle() {
isDark = !isDark;
const theme = isDark ? 'dark' : 'light';
const root = document.documentElement;
root.classList.remove('dark', 'light');
root.classList.add(theme);
try { localStorage.setItem('monsoon-theme', theme); } catch (e) {}
}
</script>
<button
type="button"
onclick={toggle}
aria-pressed={isDark}
class="rounded-lg border px-3 py-2 text-sm hover:bg-muted"
>
{isDark ? 'Light mode' : 'Dark mode'}
</button>The class: directive is also the shortest way to pin one region to a scheme: <aside class="bg-background text-foreground p-6" class:dark={alwaysDark}>.
Theme with --seed
The style: directive accepts custom properties, so the seed follows a variable:
<script>
let seed = $state('#0d9488');
</script>
<section class="rounded-2xl bg-surface p-6 space-y-4" style:--seed={seed}>
<input type="color" bind:value={seed} />
<button type="button" class="rounded-lg bg-primary text-primary-foreground px-4 py-2">Primary</button>
</section>Every semantic token (primary, secondary, accent, surfaces, muted text, borders and the focus ring) is derived from --seed in the browser. Theming lists the tokens.
Using the purge CLI
{
"scripts": {
"dev": "vite",
"css:purge": "monsooncss purge \"index.html\" \"src/**/*.{svelte,js,ts}\" -o src/monsoon.purged.css",
"build": "npm run css:purge && vite build",
"preview": "vite preview"
}
}import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { fileURLToPath } from 'node:url';
const purged = fileURLToPath(new URL('./src/monsoon.purged.css', import.meta.url));
export default defineConfig(({ command }) => ({
plugins: [svelte()],
resolve: {
alias: command === 'build' ? { monsooncss: purged } : {},
},
}));The alias only applies to vite build, so development keeps the full stylesheet and the build bundles the purged one. With the CDN setup, write to public/monsoon.css and link /monsoon.css instead.
The purge CLI reads class:ring-2={featured} as one token and does not keep ring-2 for it. Classes that appear only in class: directives must either also appear in a quoted string (as in the button above) or go in --safelist ring-2,ring-ring.
The purge CLI reads your files as plain text and keeps every class name it can see. It cannot see a name that is assembled at runtime, so write each class in full: isActive ? 'bg-primary' : 'bg-muted' survives a purge, 'bg-' + color does not. Names that only exist at runtime (from a database, a CMS or an API) go in --safelist bg-success,bg-danger.
The purged file always keeps the reset, the theme tokens, the .dark and .light scopes and the keyframes, so dark mode and --seed keep working. See the purge CLI reference for every option.
Next steps
- Browse every utility and its variants in the class reference.
- Read how the scheme is chosen in Dark mode, and how tokens are derived in Theming.
- Need routing and server rendering? See SvelteKit.
Frequently asked questions
Do Svelte scoped styles conflict with Monsoon?
No. Scoped styles only target your own selectors. Monsoon classes are global and work in every component.
Can I use the theme variables inside a Svelte style block?
Yes. Every token is a custom property, so color: var(--primary) works in component styles.