In der Frontend-Entwicklung gibt es zwei Interessengruppen. Nutzer wollen eine Seite, die gut aussieht, schnell lädt und sich vorhersehbar verhält. Entwickler wollen Code, der lesbar und wartbar bleibt. Die meisten Lösungen bevorzugen die eine Seite auf Kosten der anderen.
Ich setze auf Tailwind CSS wegen seines Utility-first-Tempos, nehme aber die Kritik von Frameworks wie Nuejs ernst: Berge von Utility-Klassen begraben die semantische Struktur deines HTML. Dieser Beitrag zeigt, wie ich beides mit CSS-Layern versöhne - Tailwinds Flexibilität, ohne sauberes Markup aufzugeben.
Was ein Design trägt
Gutes Design läuft auf ein paar Einschränkungen hinaus, die willkürliche Entscheidungen stoppen, bevor sie sich vervielfachen - das Gegenmittel zum Over-Engineering, das moderne Frontends plagt:
- Eine begrenzte Farbpalette - fünf bis sieben Kernfarben mit klaren semantischen Rollen (Marke, Neutral, Success/Warning/Error, Text). Eingeschränkte Paletten halten den Kontrast konsistent und barrierefrei.
- Typografie-Einschränkungen - ein Font-Stack mit klarer Hierarchie. Der visuelle Rhythmus ergibt sich von selbst, Nutzer erfassen die Struktur schneller.
- Ein kleiner Satz Komponenten - eine standardisierte Bibliothek schlägt einen Wildwuchs ähnlicher-aber-anderer Elemente, die Nutzer verwirren und den Wartungsaufwand aufblähen.
- Eine mathematische Abstandsskala - systematische Schritte (Zweierpotenzen funktionieren gut) statt willkürlicher Pixelwerte.
Zwei weitere Dinge zählen und kosten fast nichts. Semantisches HTML liefert
Barrierefreiheit und SEO von Haus aus - Screenreader und Suchmaschinen verstehen
<nav>, <article> und <button> bereits, eigene Komponenten werfen das weg.
Und Responsiveness ist nicht verhandelbar: Mobil macht über 60% des
Web-Traffics aus, Desktop rund ein Drittel, Tablets ein Bruchteil - auch
wenn sich der Mix je nach Branche stark verschiebt (Restaurants tendieren mobil,
SaaS tendiert Desktop).
Wo Tailwind glänzt, und wo es an Grenzen stößt
Tailwinds Preflight normalisiert Browser-Unterschiede, und von dieser Basis aus stylst du direkt im Markup, ohne Datei-Wechsel. Kompression erledigt die langen Klassenlisten in Produktion, die Performance-Sorge ist also übertrieben.
Der eigentliche Preis ist Lesbarkeit. Utility-lastiges HTML verschleiert seine
eigene Struktur und lädt still zu Inkonsistenz ein - leicht abweichende
Klassenkombinationen schleichen sich über Komponenten hinweg ein. Der Schmerz
ist in Template-Systemen wie Django größer, wo Wiederverwendung bewussten Aufwand
braucht ({% include %}), als in Komponenten-Frameworks wie Svelte oder React,
die von Haus aus kapseln.
Das Gegenargument von Nuejs
Nuejs lehnt Utility-first komplett ab
und setzt stattdessen auf modernes CSS:
@layer für die
Kontrolle der Kaskade, Custom Properties
fürs Theming, calc()
und natives Nesting.
Statt Tailwinds vollem Reset normalisiert es minimal, sodass semantische Elemente
ihr natürliches Styling behalten:
@layer settings { *, *::before, *::after { box-sizing: border-box; }
form { button, input, select, textarea { font: inherit; /* Match body text */ } }}Es bevorzugt außerdem semantische Klassennamen mit minimaler Verschmutzung - ein Modifier auf einer Basiskomponente statt einer Wand aus Utilities:
<div class="notification card"> <h3>ChitChat</h3> <p>New message</p></div>@layer components { .card { box-shadow: 0 0 2em #0001; border: var(--border); border-radius: 0.5em; padding: 1.5em; font-size: 95%;
&.notification { background: url(/img/chat.svg) 10% center no-repeat; background-size: 3rem; padding-left: 6rem; } }}Und es stylt anhand von ARIA-Attributen statt Klassen, was dich als Nebeneffekt zu barrierefreiem Markup schubst:
.accordion[aria-expanded="true"] { max-height: 100%;}
.accordion[aria-expanded="false"] { max-height: 0;}Der Ton ist scharf, aber die Sorge um langfristige Wartbarkeit ist berechtigt.
Die Versöhnung
Ich will mich nicht auf eine Seite schlagen. Tailwind hat reines CSS nie
verboten - es unterstützt Komponenten-Extraktion aktiv über
@apply
und CSS-Nesting. Also organisiere ich das gesamte Stylesheet mit Layern und nutze
jedes Werkzeug dort, wo es passt:
@import "tailwindcss";
@import "./theme.css" layer(theme);@import "./base.css" layer(base);@import "./components.css" layer(components);@import "./utilities.css" layer(utilities);
@custom-variant dark (&:where(.dark, .dark *));@plugin "@tailwindcss/forms";@plugin "@tailwindcss/typography";Vier Layer, jeder mit einer Aufgabe: ein Theme, Basis-Styles für Elemente, wiederverwendbare Komponenten und eigene Utilities. Die Plugins Forms und Typography legen gut getestete, barrierefreie Defaults obendrauf.
Theme
theme.css ist die einzige Quelle der Wahrheit für das Designsystem - Farben,
Abstände, Typografie. Farben in :root zu definieren (mit einer dunklen
@variant) und sie dann in @theme zu mappen, lässt einen Variablen-Tausch das
ganze Theme umstellen:
:root { --foreground: oklch(0.15 0.01 270); /* Rich dark */ --background: oklch(0.99 0.002 270); /* Pure white with hint */ --primary: oklch(0.55 0.18 262); /* Rich blue */ --secondary: oklch(0.6 0.14 285); /* Purple-blue */ --success: oklch(0.7 0.16 142); /* Fresh green */ --error: oklch(0.65 0.2 25); /* Vibrant red */ --warning: oklch(0.8 0.15 75); /* Warm amber */ --muted: oklch(0.55 0.03 270); /* Neutral gray */
@variant dark { --foreground: oklch(0.95 0.01 270); /* Near white with warmth */ --background: oklch(0.05 0.002 270); /* Deep dark with blue hint */ --primary: oklch(0.7 0.15 262); /* Brighter blue for contrast */ --secondary: oklch(0.72 0.12 285); /* Lighter purple-blue */ --success: oklch(0.75 0.14 142); /* Brighter green */ --warning: oklch(0.85 0.13 75); /* Brighter amber */ --error: oklch(0.7 0.18 25); /* Softer red */ --muted: oklch(0.45 0.02 270); /* Muted gray for dark mode */ }}
@theme { --color-primary: var(--primary); --color-secondary: var(--secondary); --color-success: var(--success); --color-error: var(--error); --color-warning: var(--warning); --color-foreground: var(--foreground); --color-background: var(--background); --color-muted: var(--muted);
/* Typography system */ --font-sans: Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif; --font-serif: Rockwell, "Rockwell Nova", "Roboto Slab", "DejaVu Serif", "Sitka Small", serif; --font-mono: "JetBrains Mono", ui-monospace, monospace;}Das Präfix --color-* ist wichtig: Tailwind erzeugt Utilities wie
bg-background und text-foreground aus Variablen, die so benannt sind.
Basis-Styles
base.css setzt Defaults für semantische HTML-Elemente - Hintergrund,
Textfarbe, Überschriften, Links. Es hält die Darstellung konsistent, während das
HTML aussagekräftig bleibt:
/* Minimal base styles */html { @apply scroll-smooth;}
body { @apply bg-background text-foreground font-sans;}Komponenten
In components.css leben die wiederverwendbaren Muster, die Tailwind-Utilities
dort mit eigenem CSS mischen, wo es nötig ist:
/* Button components */.btn { @apply px-4 py-2 rounded-lg font-medium transition-colors; @apply focus:outline-2 focus:outline-offset-2 focus:outline-primary;
&.btn-primary { @apply bg-primary text-white hover:bg-primary/90; }
&.btn-secondary { @apply bg-secondary text-white hover:bg-secondary/90; }
&:disabled { @apply opacity-50 cursor-not-allowed; }}Bei größeren Projekten teilst du das in eine Datei pro Komponente auf:
@import "./button.css";@import "./card.css";@import "./form.css";@import "./navigation.css";Utilities
utilities.css enthält eigene Utilities für die Lücken, die Tailwind nicht
abdeckt:
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none;
&::-webkit-scrollbar { display: none; }}
/* Screen reader only utility */.sr-only { @apply absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0; clip: rect(0, 0, 0, 0);}Weil jeder Style in genau einem Layer und an genau einem Ort liegt, wird die Wahl zwischen reinem CSS und einer Utility zur Frage des Kontexts, nicht der Architektur.
Fazit
Bootstrap ist am schnellsten, wenn du Standardkomponenten brauchst; es wird einengend, sobald die Marke Spezifisches verlangt. Tailwind kauft diese Flexibilität zurück, auf Kosten der HTML-Lesbarkeit. DaisyUI sitzt dazwischen, und sein Modell aus semantischer Komponente plus Utility spiegelt das hier gezeigte Layer-Setup.
Das Hybrid gewinnt, weil es die Belange trennt: CSS besitzt die Darstellung, HTML besitzt Struktur und Barrierefreiheit. Du bekommst Tailwinds Tempo und eine Codebasis, die sich auch ein Jahr später noch sauber liest.
Im Kern sollte es bei Webdesign um Worte gehen. Worte kommen nicht, nachdem das Design fertig ist. Worte sind der Anfang, der Kern, der Fokus.
-- Words
Siehe auch: Motherfucking Website - eine satirische Kritik an over-engineerter Webentwicklung.