Tailwind ist im Svelte-Ökosystem so verbreitet wie überall sonst — schnelles Stylen, kleine Bundles, ein klares Design-System per Konfig. In Svelte funktioniert es ohne nennenswerte Sondereffekte: Tailwind-Klassen sind globale CSS-Klassen, die das Style-Scoping nicht stören. Dieser Artikel zeigt das Setup, das Zusammenspiel mit dem Scoping und die zwei wichtigsten Patterns für lange Klassenlisten.
Setup mit dem sv-CLI
Wer sein Projekt mit npx sv create my-app neu aufgesetzt hat, konnte Tailwind direkt im Wizard auswählen. Wer es nachrüsten will, lässt den sv add-Befehl die nötigen Schritte erledigen:
npx sv add tailwindcssDas CLI macht drei Dinge:
- Es installiert die Pakete (
tailwindcss, das Vite-Plugin, ggf. PostCSS). - Es legt eine
tailwind.config-Datei an oder ergänzt dievite.config.tsum das Plugin. - Es importiert Tailwind in deiner zentralen CSS-Datei.
Danach kannst du Klassen wie p-4, text-lg, bg-slate-100 direkt in jeder .svelte-Datei verwenden.
Wie Tailwind und Sveltes Scoping zusammenspielen
Sveltes Style-Scoping wirkt nur auf den <style>-Block einer Komponente. Tailwind-Klassen kommen aus einem globalen Stylesheet — der ist vom Scoping unbeeinflusst. Das heißt:
- Du schreibst
class="p-4 bg-slate-100"in jede Komponente, die Klassen funktionieren ganz normal. - Es gibt keine Doppelung, keinen Hash, keine Optimierungsstufe — Tailwind wird vom Vite-Plugin verarbeitet und ist beim Browser-Rendern fertig.
- Eigene Klassen im
<style>-Block der Komponente bleiben weiterhin gescoped. Beide Welten existieren parallel.
Eine Beispiel-Komponente:
<script>
let { title, children } = $props();
</script>
<article class="rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
<h2 class="mb-2 text-lg font-semibold text-slate-900">
{title}
</h2>
<div class="text-slate-700 leading-relaxed">
{@render children()}
</div>
</article>Kein <style>-Block, kein Scoping nötig — Tailwind macht die Arbeit.
@apply im scoped Style-Block
Wenn dieselbe Tailwind-Klassen-Kombination an mehreren Stellen vorkommt, muss man sie nicht jedes Mal wiederholen. Innerhalb des scoped <style>-Blocks kannst du sie zu einem benannten Selektor zusammenfassen:
<article class="card">
<h2 class="title">{title}</h2>
<p class="body">{body}</p>
</article>
<style>
@reference "tailwindcss";
.card {
@apply rounded-lg border border-slate-200 bg-white p-4 shadow-sm;
}
.title {
@apply mb-2 text-lg font-semibold text-slate-900;
}
.body {
@apply text-slate-700 leading-relaxed;
}
</style>Die .card-, .title-, .body-Klassen sind weiterhin gescoped zur Komponente — du holst dir nur die Tailwind-Werte rein. Das @reference-Statement am Anfang des Style-Blocks ist seit Tailwind v4 nötig, damit der Compiler weiß, dass Tailwind-Direktiven hier verwendet werden.
Das ist die saubere Lösung, wenn dieselben fünf bis zehn Klassen immer zusammen auftauchen.
Klassen-Listen werden zu lang — was tun?
Tailwind ist großartig schnell beim Prototypen. Sobald aber class="..." länger wird als der eigentliche Inhalt, leidet die Lesbarkeit.
<button class="inline-flex items-center justify-center gap-2 rounded-md bg-slate-900 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-slate-900 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none">
Speichern
</button>Dafür gibt es drei Patterns, die in Svelte besonders gut funktionieren.
Pattern 1 – Eigene Komponente
Die simpelste und wirksamste Lösung. Der Button kommt einmal in eine Button.svelte mit Tailwind-Klassen — überall sonst rufst du nur <Button>...</Button> auf.
<script>
let { variant = 'primary', children, ...rest } = $props();
</script>
<button
class={[
'inline-flex items-center justify-center gap-2 rounded-md px-4 py-2',
'text-sm font-semibold transition-colors',
'focus:outline-none focus:ring-2 focus:ring-offset-2',
'disabled:opacity-50 disabled:pointer-events-none',
variant === 'primary' && 'bg-slate-900 text-white hover:bg-slate-800',
variant === 'danger' && 'bg-red-600 text-white hover:bg-red-700',
]}
{...rest}
>
{@render children()}
</button>Svelte akzeptiert ein Array als class-Wert: Falsy-Einträge werden übersprungen, der Rest wird zu einem String verbunden. Damit kannst du Klassen sauber in mehrere Zeilen aufteilen, ohne clsx-Library.
Pattern 2 – clsx mit Map-Form
Wer es klassisch gewohnt ist:
<script>
import clsx from 'clsx';
let { variant = 'primary', loading } = $props();
</script>
<button
class={clsx(
'btn',
{
'btn-primary': variant === 'primary',
'btn-danger': variant === 'danger',
'btn-loading': loading,
},
)}
>
…
</button>Funktioniert ebenfalls — und manchmal liest sich die Map-Form sauberer als ein Array mit &&-Konstrukten.
Pattern 3 – @apply (siehe oben)
Wenn die Logik nicht variantengetrieben ist, sondern einfach eine wiederkehrende Klassen-Komposition, ist @apply im scoped Block die kompakteste Lösung. Drei Zeilen Style sind oft lesbarer als achtzig Klassen im Markup.
Tailwind-Plugins, die in Svelte gut funktionieren
Ein paar Tools aus dem Tailwind-Ökosystem lohnen sich besonders:
@tailwindcss/forms— vernünftiger Default-Look für Form-Elemente, ohne Reset-Frust.@tailwindcss/typography— erzeugt eine.prose-Klasse für Lesetexte (Markdown-Output, Blog-Artikel).tailwind-merge— löst Konflikte auf, wenn dieselbe Property doppelt im Klassen-String steht (p-2 p-4→p-4gewinnt). Nützlich beim Weiterreichen von Klassen über Wrapper.class-variance-authority(CVA) — wenn du eine Komponente mit vielen Varianten hast und die Klassen typisiert konfigurieren willst.
Alle vier laufen unverändert in Svelte — sie sind reine Tailwind-/CSS-Tools, kein Framework-spezifischer Code.
Häufige Stolperfallen
Klasse aus einem dynamisch gebildeten String — wird vom JIT nicht erkannt.
<div class="bg-{color}-500"></div>Tailwind scannt deine Source-Files nach Klassennamen, um nur die genutzten zu generieren. Eine Klasse, die per String-Konkatenation entsteht, ist im Source nicht als ganzer Klassenname zu sehen — Tailwind kennt sie nicht und purged sie weg. Lösung: Vollständige Klassen-Strings im Source haben oder per Map mappen ({ red: 'bg-red-500', blue: 'bg-blue-500' }[color]).
@apply ohne @reference in Svelte-Style-Block.
Seit Tailwind v4 muss in scoped <style>-Blöcken explizit angegeben werden, dass Tailwind-Direktiven verwendet werden. Mit @reference "tailwindcss"; am Anfang.
Tailwind-Klassen mit eigenen Klassen kombinieren — Reihenfolge stimmt nicht.
<button class="my-btn p-2">…</button>
<style>
.my-btn { padding: 1rem; }
</style>Die scoped .my-btn-Regel ist spezifischer als die Tailwind-Klasse — sie gewinnt. Wenn du wirklich Tailwind das letzte Wort geben willst, nutze !important oder kehre die Reihenfolge um (Tailwind als gescopte @apply).
Sehr lange Klassenlisten beim Linting/Formatting. Der offizielle Prettier-Plugin für Tailwind sortiert Klassen automatisch in eine kanonische Reihenfolge. Macht Diffs lesbarer und einheitlich.
Dunkle Themes mit Klassen-Schalter.
Tailwinds dark:-Variante kann auf [data-theme='dark'] oder .dark gehängt werden — über die Tailwind-Config (darkMode: 'selector'). Passt besser zu Sveltes Theme-Toggle (siehe CSS-Variablen-Artikel).