CSS

Cascading Style Sheets

White Space und Text-Wrapping in CSS – eine ausführliche Anleitung ✨

Wenn es um Textdarstellung in CSS geht, sind zwei Themen besonders wichtig:

  1. Wie Leerzeichen, Zeilenumbrüche und Tabulatoren behandelt werden
  2. Wie und wo Text umbrochen werden darf

Genau dafür gibt es in CSS mehrere Eigenschaften rund um White Space und Text Wrap. Manche davon sind altbekannt, andere stammen aus moderneren CSS-Spezifikationen. Zusammen steuern sie, ob Text in einer Zeile bleibt, umbricht, Leerzeichen zusammenfasst, lange Wörter trennt oder über den Container hinausragt.


Überblick: Was gehört thematisch dazu?

Die wichtigsten CSS-Eigenschaften in diesem Bereich sind:

  1. white-space
  2. overflow-wrap
  3. word-break
  4. line-break
  5. hyphens
  6. text-wrap
  7. tab-size

Außerdem gibt es verwandte Themen, die oft damit verwechselt oder gemeinsam eingesetzt werden:

  1. writing-mode
  2. direction
  3. text-overflow
  4. overflow
  5. display und verfügbare Breite des Elements

Das Grundproblem: Warum braucht man diese Eigenschaften?

Standardmäßig behandelt der Browser normalen Fließtext ungefähr so:

Beispiel:

<p>
  Das    ist      Text
  mit mehreren Leerzeichen
  und einem Zeilenumbruch im HTML.
</p>

Ohne besondere CSS-Regeln wird das im Browser ungefähr wie ein normaler Satz dargestellt – also nicht mit exakt denselben Leerzeichen und Zeilenumbrüchen wie im Quelltext.


white-space – die zentrale Eigenschaft

Die wichtigste Eigenschaft für White Space ist white-space. Sie steuert vor allem:

Syntax

.element {
  white-space: normal;
}

Die wichtigsten Werte

white-space: normal

Das ist das Standardverhalten.

p {
  white-space: normal;
}

Typischer Einsatz: normaler Fließtext.


white-space: nowrap

.badge {
  white-space: nowrap;
}

Effekt: Der gesamte Text bleibt in einer Zeile, sofern nicht explizit ein <br> oder ähnliches vorhanden ist.

Typische Einsätze:

Achtung: In schmalen Containern kann das zu horizontalem Overflow führen.


white-space: pre

Dieses Verhalten ähnelt dem HTML-Element <pre>.

pre,
.code-like {
  white-space: pre;
}

Typischer Einsatz:


white-space: pre-wrap

.message {
  white-space: pre-wrap;
}

Das ist sehr praktisch für Inhalte wie:

Denn damit bleiben manuelle Zeilenumbrüche erhalten, aber lange Zeilen können dennoch umbrechen.


white-space: pre-line

.poem {
  white-space: pre-line;
}

Unterschied zu pre-wrap:


white-space: break-spaces

Ein moderner Wert mit sehr speziellem Verhalten.

.output {
  white-space: break-spaces;
}

Dieser Wert ist nützlich, wenn wirklich die sichtbare Struktur von Leerzeichen wichtig ist.


Vergleichstabelle zu white-space

Wert Leerzeichen erhalten? Zeilenumbrüche erhalten? automatischer Umbruch?
normal Nein Nein Ja
nowrap Nein Nein Nein
pre Ja Ja Nein
pre-wrap Ja Ja Ja
pre-line Nein Ja Ja
break-spaces Ja Ja Ja

Moderne Aufteilung: White-Space-Untereigenschaften

In neueren CSS-Spezifikationen wird white-space konzeptionell in feinere Teilaspekte aufgeteilt. Dazu gehören unter anderem:

  1. white-space-collapse
  2. text-wrap-mode
  3. white-space-trim

Diese sind konzeptionell wichtig, aber noch nicht überall gleich gut etabliert wie white-space. In der Praxis verwendet man deshalb meist weiterhin white-space.


white-space-collapse

Diese Eigenschaft steuert, wie Leerraum zusammengefasst oder erhalten wird.

Mögliche Werte sind je nach Spezifikation unter anderem:

Beispielidee:

.element {
  white-space-collapse: preserve;
}

Praxis-Hinweis: Diese Eigenschaft ist interessant für moderne CSS-Modelle, aber für produktive, breit kompatible Websites ist white-space oft die sicherere Wahl.


text-wrap-mode

Sie beschreibt grundsätzlich, ob Zeilenumbruch erlaubt ist.

Typische Werte:

Beispiel:

.element {
  text-wrap-mode: nowrap;
}

Auch hier gilt: In der Praxis ist meist white-space: nowrap; der bekanntere und breiter eingesetzte Weg.


white-space-trim

Diese Eigenschaft soll beeinflussen, ob bestimmte Whitespaces an Anfang oder Ende entfernt werden.

Beispielhaft konzeptionell:

.element {
  white-space-trim: discard-before;
}

Auch das ist eher ein fortgeschrittenes bzw. modernes Thema mit eingeschränkter Relevanz im Alltag.


overflow-wrap – was passiert mit langen Wörtern?

overflow-wrap bestimmt, ob der Browser lange Wörter oder Zeichenketten umbrechen darf, wenn sie sonst den Container sprengen würden.

Früher war dafür oft word-wrap im Einsatz. Das ist heute im Grunde ein Alias für overflow-wrap.

Syntax

.element {
  overflow-wrap: normal;
}

Werte

overflow-wrap: normal

Der Browser bricht nur an normalen Umbruchstellen um.

.element {
  overflow-wrap: normal;
}

Lange URLs oder zusammengesetzte Wörter können dann überlaufen.


overflow-wrap: break-word

Falls nötig, darf ein langes Wort umgebrochen werden, um Overflow zu verhindern.

.article {
  overflow-wrap: break-word;
}

Typischer Einsatz:


overflow-wrap: anywhere

Noch aggressiver: Der Browser darf an praktisch jeder Stelle umbrechen, wenn nötig.

.article {
  overflow-wrap: anywhere;
}

Wann sinnvoll?

Unterschied zu break-word:
anywhere erlaubt Umbrüche sehr frei und berücksichtigt diese Möglichkeiten auch stärker bei der Zeilenberechnung.


word-wrap – historischer Alias

.element {
  word-wrap: break-word;
}

Das funktioniert oft noch, aber modern und sauber ist:

.element {
  overflow-wrap: break-word;
}

word-break – wie aggressiv darf in Wörtern gebrochen werden?

word-break beeinflusst den Umbruch innerhalb von Wörtern stärker als overflow-wrap.

Syntax

.element {
  word-break: normal;
}

Werte

word-break: normal

Normales Umbruchverhalten nach Sprach- und Schriftsystemregeln.

.element {
  word-break: normal;
}

word-break: break-all

Wörter dürfen praktisch an beliebigen Stellen getrennt werden.

.element {
  word-break: break-all;
}

Effekt: Auch normale Wörter können mitten im Wort umbrechen.

Das löst Overflow-Probleme zuverlässig, sieht aber oft unschön aus.

Einsatz nur mit Vorsicht, z. B. bei:


word-break: keep-all

Verhindert Wortumbrüche innerhalb von Wörtern, besonders relevant für ostasiatische Schriftsysteme.

.element {
  word-break: keep-all;
}

Für deutschsprachige Seiten ist dieser Wert seltener relevant, kann aber in internationalen Projekten wichtig sein.


Unterschied zwischen overflow-wrap und word-break

Das ist einer der häufigsten Stolperpunkte.

overflow-wrap

word-break

Faustregel ✅

  1. Erst overflow-wrap prüfen
  2. Nur wenn das nicht reicht, word-break einsetzen
  3. break-all nur bewusst und sparsam verwenden

hyphens – automatische Silbentrennung

Mit hyphens kann der Browser Wörter an geeigneten Stellen trennen – oft mit Bindestrich.

Syntax

.element {
  hyphens: auto;
}

Werte

hyphens: none

Keine Silbentrennung.

.element {
  hyphens: none;
}

hyphens: manual

Nur manuell vorgegebene Trennstellen werden verwendet.

.element {
  hyphens: manual;
}

Manuelle Trennstellen können z. B. im HTML gesetzt werden.


hyphens: auto

Automatische Silbentrennung nach Sprachregeln, sofern der Browser und die Spracheinstellungen das unterstützen.

p {
  hyphens: auto;
}

Wichtig ist dabei oft ein korrekt gesetztes lang-Attribut:

<html lang="de">

oder

<p lang="de">Donaudampfschifffahrtsgesellschaftskapitän</p>

Ohne passende Sprache kann automatische Silbentrennung unzuverlässig sein.


Wann ist hyphens nützlich?

Beispiel

.article-text {
  hyphens: auto;
  overflow-wrap: normal;
}

Das führt oft zu schöneren Ergebnissen als brutal mit word-break: break-all zu arbeiten.


line-break – Regeln für Zeilenumbrüche, besonders in asiatischen Schriften

line-break steuert die Strenge der Umbruchregeln, insbesondere für chinesische, japanische und koreanische Texte.

Syntax

.element {
  line-break: auto;
}

Typische Werte

Beispiel:

.element {
  line-break: strict;
}

Für deutschsprachige Seiten ist diese Eigenschaft meist nicht zentral, aber in mehrsprachigen Projekten kann sie wichtig sein.


text-wrap – moderne Steuerung für Zeilenumbruch

text-wrap ist eine modernere Eigenschaft für Strategien des Textumbruchs.

Syntax

.element {
  text-wrap: wrap;
}

Mögliche Werte

Je nach aktuellem Implementierungsstand sind insbesondere diese relevant:

Nicht jeder Wert wird in jedem Browser gleich unterstützt.


text-wrap: wrap

Normales Umbruchverhalten.

.element {
  text-wrap: wrap;
}

text-wrap: nowrap

Kein automatischer Umbruch.

.element {
  text-wrap: nowrap;
}

Praktisch ähnlich zu white-space: nowrap, aber konzeptionell moderner auf den Umbruch fokussiert.


text-wrap: balance

Versucht, Zeilen ausgewogener zu verteilen. Besonders nützlich für Überschriften.

h1, h2 {
  text-wrap: balance;
}

Sehr sinnvoll für:

Statt einer sehr langen und einer sehr kurzen Zeile versucht der Browser, ein harmonischeres Ergebnis zu erzeugen.


text-wrap: pretty

Zielt auf optisch angenehmere Umbrüche ab und versucht unschöne Ein-Zeilen-Wörter oder ungünstige Brüche zu vermeiden.

p {
  text-wrap: pretty;
}

Die Unterstützung kann je nach Browserstand variieren.


text-wrap: stable

Soll Umbruchverhalten stabil halten, z. B. bei bearbeitbaren Inhalten. Auch das ist eher ein fortgeschrittenes Thema.


tab-size – Breite von Tabulatoren

Wenn ein Text Tab-Zeichen enthält, kann tab-size deren visuelle Breite bestimmen.

Syntax

.element {
  tab-size: 4;
}

oder

.element {
  tab-size: 2;
}

Beispiel

pre {
  white-space: pre;
  tab-size: 4;
}

Das ist vor allem für Code, Logs oder vorformatierte Texte nützlich.


Wichtige verwandte Eigenschaften

text-overflow

Wenn Text nicht umbrochen wird und der Container zu klein ist, kann text-overflow festlegen, wie abgeschnittener Text dargestellt wird.

Typisch:

.ellipsis {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

Das erzeugt die bekannte Darstellung mit „…“.

Wichtig: text-overflow funktioniert typischerweise nur in Kombination mit:

  1. begrenzter Breite
  2. overflow: hidden
  3. meist white-space: nowrap

overflow

Wenn Text nicht in den Container passt, beeinflusst overflow, ob er sichtbar bleibt, abgeschnitten wird oder Scrollbars erscheinen.

.box {
  overflow: auto;
}

Mögliche Werte:


display und Breite

Textumbruch hängt nicht nur von Text-Eigenschaften ab, sondern auch davon, ob überhaupt eine begrenzte Breite existiert.

Beispiel:

.inline-label {
  display: inline;
}

Ein rein inline dargestelltes Element verhält sich anders als ein Block mit fester oder maximaler Breite.

Oft braucht man für sichtbaren Umbruch:

.card-title {
  display: block;
  max-width: 20rem;
}

Typische Praxisrezepte 💡

1. Normaler Fließtext

p {
  white-space: normal;
  overflow-wrap: break-word;
  hyphens: auto;
}

Gut für: Artikel, Blogposts, CMS-Inhalte

Vorteil: normale Darstellung, aber robuste Behandlung langer Wörter.


2. Lange URLs oder unkontrollierter User-Content

.user-content {
  overflow-wrap: anywhere;
}

Gut für: Kommentare, Foren, Chat, Markdown-Content


3. Eine Zeile mit Auslassungspunkten

.one-line-ellipsis {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

4. Vorformatierter Text oder Code

pre {
  white-space: pre;
  tab-size: 4;
  overflow: auto;
}

Wenn Code stattdessen umbrechen soll:

pre.wrap {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

5. Benutzereingaben mit erhaltenen Zeilenumbrüchen

.user-message {
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

Das ist ein sehr typisches und sinnvolles Setup.


6. Schöne Überschriften umbrechen

h1, h2, h3 {
  text-wrap: balance;
}

Fallback-orientiert kann man einfach zusätzlich auf normales Verhalten vertrauen, falls der Browser den Wert nicht unterstützt.


Häufige Stolperfallen

1. nowrap „funktioniert zu gut“

.badge {
  white-space: nowrap;
}

Dann bleibt wirklich alles in einer Zeile. Wenn der Container schmal ist, läuft der Text möglicherweise heraus.

Lösung: Nur dort einsetzen, wo es wirklich gewünscht ist.


2. text-overflow: ellipsis zeigt keine Punkte

Nur diese Regel reicht nicht:

.element {
  text-overflow: ellipsis;
}

Meist braucht man zusätzlich:

.element {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

Und oft auch eine definierte Breite oder max. Breite.


3. Lange Wörter brechen trotzdem nicht um

Wenn ein langes Wort oder eine URL herausragt, hilft oft:

.element {
  overflow-wrap: break-word;
}

oder noch robuster:

.element {
  overflow-wrap: anywhere;
}

4. hyphens: auto bringt nichts

Dann fehlt oft eines der folgenden Dinge:

  1. Das richtige lang-Attribut
  2. Browser-Unterstützung
  3. Geeigneter Fließtext-Kontext mit tatsächlichem Umbruchbedarf

5. In Flex- oder Grid-Layouts bricht Text nicht wie erwartet um

Das Problem liegt oft nicht an white-space, sondern an den Mindestgrößen der Items.

Ein klassischer Fall:

.flex-item {
  overflow-wrap: break-word;
}

Und trotzdem bricht der Text nicht schön um.

Dann kann zusätzlich nötig sein:

.flex-item {
  min-width: 0;
}

Oder in Grid-Layouts ebenfalls eine passende Größenlogik.

Das ist ein sehr häufiger Praxisfehler.


Unterschiede kurz und prägnant zusammengefasst

Wenn du Leerzeichen und Zeilenumbrüche aus dem Quelltext steuern willst

white-space

Wenn lange Wörter oder URLs den Container sprengen

overflow-wrap

Wenn innerhalb von Wörtern aggressiver getrennt werden soll

word-break

Wenn automatische Silbentrennung gewünscht ist

hyphens

Wenn Überschriften schöner umbrechen sollen

text-wrap: balance

Wenn Tabs in vorformatiertem Text korrekt aussehen sollen

tab-size


Empfehlenswerte Standardstrategien

Für normalen Content

.content {
  white-space: normal;
  overflow-wrap: break-word;
  hyphens: auto;
}

Für User-Generated Content

.user-content {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

Das bewahrt Eingabe-Zeilenumbrüche und verhindert Layoutbruch durch lange Tokens.


Für Titel

.title {
  text-wrap: balance;
}

Für einzeilige UI-Elemente

.chip {
  white-space: nowrap;
}

Für abgeschnittene Ein-Zeilen-Texte

.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

Beispiel: alles in einer kleinen Demo

<div class="demo">
  <p class="normal">
    Das ist ein normaler Fließtext mit einer sehrlangenbeispielzeichenketteohnesinnvollenumbruch.
  </p>

  <p class="message">
    Hallo!
    Dies ist eine Nachricht
    mit manuellem Zeilenumbruch.
  </p>

  <p class="truncate">
    Dies ist ein sehr langer Titel, der in einer Zeile abgeschnitten werden soll.
  </p>

  <pre class="code">function test() {
\tconsole.log("Hallo");
}</pre>
</div>
.demo {
  max-width: 20rem;
  font-family: system-ui, sans-serif;
}

.normal {
  white-space: normal;
  overflow-wrap: break-word;
  hyphens: auto;
}

.message {
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.code {
  white-space: pre;
  tab-size: 4;
  overflow: auto;
}

Browser-Kompatibilität – was ist besonders sicher?

Sehr gut etabliert und in der Praxis „sicher“:

Moderner bzw. teils mit Vorsicht zu prüfen:

Empfehlung: Für produktive Projekte moderne Features gern nutzen, aber mit sinnvollen Fallbacks.


Eine praktische Merkhilfe 🧠

Du kannst dir das so merken:

  1. white-space
    „Wie behandle ich Leerzeichen und Zeilenumbrüche grundsätzlich?“

  2. overflow-wrap
    „Was mache ich mit langen Wörtern, wenn sie sonst überlaufen?“

  3. word-break
    „Darf ich Wörter auch mitten drin zerlegen?“

  4. hyphens
    „Kann ich Wörter sprachgerecht trennen?“

  5. text-wrap
    „Welche Strategie soll der Browser für den Umbruch wählen?“

  6. tab-size
    „Wie breit sind Tabs in vorformatiertem Text?“


Fazit

Die wichtigste Eigenschaft für White Space ist white-space. Sie legt die Basis dafür, wie Text, Leerzeichen und Zeilenumbrüche verarbeitet werden. Für robuste Layouts reicht das allein aber oft nicht aus.

In der Praxis ist diese Kombination besonders häufig sinnvoll:

.element {
  white-space: normal;
  overflow-wrap: break-word;
  hyphens: auto;
}

Für Benutzereingaben dagegen oft eher:

.element {
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

Und für moderne, schön umbrechende Überschriften:

h1, h2 {
  text-wrap: balance;
}

Wenn du möchtest, kann ich dir im nächsten Schritt noch eine kompakte Cheat Sheet-Tabelle, eine Browser-Kompatibilitätsübersicht oder eine visuelle Demo mit HTML/CSS-Beispielen für jeden Wert erstellen.

SASS Mixin für typografische Kontrolle in Bricks Builder

Ich nutze den BricksBuilder und dort kann ich dank SASS (durch das Plugin Advanced Themer) Mixins verwenden. Also dort gibt es ja folgende Syntax:

%root% {
	color: red;
}

Kann du mir ein Mixin schreiben, das ich einfach in beliebige Klassen einfügen kann und das folgendes macht?:

Es soll 1. einen Parameter entgegen nehmen (1, 2 oder 3):

  1. Wörter brechen typografisch korrekt um, so wie es in der gerade aktiven Sprache (auf der Website) korrekt ist. D.h. also "Mund-schenk" beispielsweise (im Deutschen).
  2. Es finden keine Umbrüche mitten in Wörtern statt (nur ganze Wörter), außer die gesamte Zeile reicht für ein einziges Wort nicht aus, dann soll es wie bei 1. umbrochen werden.
  3. Umbrüche finden gar nicht mehr automatisch statt. Nur manuelle Umbrüche (z.B. <br>) funktionieren.

Dann soll das Mixing auch noch unabhängig vom gewählten Parameter-Wert festlegen, dass spracheabhängige typografische Zeichen gesetzt werden: Beispielsweise im Deutschen statt "" dann „“, statt '' dann ‚‘, statt -- dann , usw.

Diese Regeln sollen aber intelligent gesetzt werden, d.h. z.B. nicht in pre-Elementen usw.


Hier ist ein umfassendes Mixin für deine Anforderungen:

// =============================================
// TYPOGRAFIE MIXIN
// =============================================
// Parameter:
// 1 = Silbentrennung (sprachabhängig korrekt)
// 2 = Nur ganze Wörter (Fallback auf Silbentrennung)
// 3 = Keine automatischen Umbrüche
// =============================================

@mixin typo-wrap($mode: 2) {
  // =========================================
  // GEMEINSAME EINSTELLUNGEN (immer aktiv)
  // =========================================

  // Sprache für Anführungszeichen und Trennung erkennen
  // Browser nutzt das lang-Attribut des HTML-Elements

  // Typografische Anführungszeichen aktivieren
  // Deutsch: „" und ‚'
  // Englisch: "" und ''
  // Französisch: «» und ‹›
  // etc. - Browser wählt automatisch basierend auf lang=""
  quotes: auto;

  // Typografische Optimierungen
  font-variant-numeric: oldstyle-nums; // Optional: Mediävalziffern
  font-kerning: auto;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;

  // Witwen und Waisen vermeiden (moderne Browser)
  text-wrap: pretty;

  // =========================================
  // UMBRUCH-MODI
  // =========================================

  @if $mode == 1 {
    // MODUS 1: Silbentrennung (sprachabhängig)
    // Wörter werden an korrekten Silbengrenzen getrennt
    hyphens: auto;
    -webkit-hyphens: auto;
    -ms-hyphens: auto;
    hyphenate-limit-chars: 6 3 2; // Min. Wortlänge, vor Trennung, nach Trennung
    hyphenate-limit-lines: 2; // Max. aufeinanderfolgende getrennte Zeilen
    hyphenate-limit-zone: 8%; // Zone am Zeilenende für Trennung
    word-wrap: break-word;
    overflow-wrap: break-word;
    word-break: normal;

  } @else if $mode == 2 {
    // MODUS 2: Ganze Wörter bevorzugen
    // Umbruch nur zwischen Wörtern, Silbentrennung nur als Fallback
    hyphens: auto;
    -webkit-hyphens: auto;
    -ms-hyphens: auto;
    overflow-wrap: break-word;
    word-wrap: break-word;
    word-break: normal;
    // Strengere Trennregeln
    hyphenate-limit-chars: 12 6 4; // Nur lange Wörter trennen
    hyphenate-limit-lines: 1;
    hyphenate-limit-zone: 20%; // Größere Zone = weniger Trennungen

  } @else if $mode == 3 {
    // MODUS 3: Keine automatischen Umbrüche
    // Nur manuelle Umbrüche (<br>, \A) funktionieren
    hyphens: none;
    -webkit-hyphens: none;
    -ms-hyphens: none;
    white-space: normal;
    word-break: keep-all;
    overflow-wrap: normal;
    word-wrap: normal;
  }

  // =========================================
  // AUSNAHMEN für Code-Elemente
  // =========================================

  pre,
  code,
  kbd,
  samp,
  var,
  tt,
  [class*="code"],
  [class*="pre"],
  .wp-block-code,
  .wp-block-preformatted {
    // Keine typografischen Ersetzungen in Code
    quotes: none;
    hyphens: none;
    -webkit-hyphens: none;
    font-feature-settings: normal;
    font-variant-numeric: normal;
    // Code darf umbrechen wenn nötig
    white-space: pre-wrap;
    word-break: break-all;
    overflow-wrap: break-word;
  }
}

// =============================================
// ZUSATZ: Typografische Zeichen per CSS
// =============================================
// Für echte Zeichenersetzung (-- → –) benötigst du
// serverseitiges Processing (PHP/JS), aber CSS kann
// helfen mit Anführungszeichen:

// =============================================
// SHORTHAND MIXINS
// =============================================

@mixin typo-hyphenate {
  @include typo-wrap(1);
}

@mixin typo-whole-words {
  @include typo-wrap(2);
}

@mixin typo-no-break {
  @include typo-wrap(3);
}

Anwendung in Bricks Builder

// Beispiel 1: Fließtext mit Silbentrennung
%root% {
  @include typo-wrap(1);
}

// Beispiel 2: Überschriften - ganze Wörter
%root% {
  @include typo-wrap(2);
}

// Beispiel 3: Buttons/Labels - keine Umbrüche
%root% {
  @include typo-wrap(3);
}

// Oder mit Shorthand:
%root% {
  @include typo-hyphenate;    // = Modus 1
  @include typo-whole-words;  // = Modus 2
  @include typo-no-break;     // = Modus 3
}

Wichtige Hinweise

1. HTML-Sprachattribut setzen

Damit die automatische Silbentrennung funktioniert, muss das lang-Attribut gesetzt sein:

<html lang="de">  <!-- Deutsch -->
<html lang="en">  <!-- Englisch -->
<html lang="de-CH">  <!-- Schweizerdeutsch -->

2. Gedankenstriche (-- → –)

CSS kann keine Textzeichen ersetzen. Für automatische Ersetzung von -- zu benötigst du:

Option A: JavaScript (clientseitig)

// In Bricks unter "Page Settings > Custom Code > Body Scripts"
document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, td, th, blockquote')
  .forEach(el => {
    if (!el.closest('pre, code, script, style')) {
      el.innerHTML = el.innerHTML
        .replace(/--/g, '–')
        .replace(/\.\.\./g, '…');
    }
  });

Option B: WordPress-Filter (serverseitig, empfohlen)

// In functions.php oder Code-Snippets Plugin
add_filter('the_content', 'typografie_korrektur', 99);
add_filter('the_title', 'typografie_korrektur', 99);

function typografie_korrektur($content) {
    $replacements = [
        '--' => '–',      // Gedankenstrich
        '---' => '—',     // Geviertstrich
        '...' => '…',     // Auslassungspunkte
        '(c)' => '©',
        '(r)' => '®',
        '(tm)' => '™',
    ];
    return str_replace(
        array_keys($replacements), 
        array_values($replacements), 
        $content
    );
}

3. Browser-Support

Feature Chrome Firefox Safari Edge
hyphens: auto
quotes: auto
hyphenate-limit-* ⚠️ ⚠️
text-wrap: pretty

Die Basis-Funktionalität wird von allen modernen Browsern unterstützt!

OKLCH

OKLCH

HSL-L ist nicht „wahrgenommene Helligkeit“ 🎨

Der L-Wert in HSL ist nur eine geometrische Größe innerhalb dieses Farbmodells, aber nicht so definiert, dass Menschen Farben bei gleichem L als gleich hell wahrnehmen.

Ein typisches Beispiel:

können in HSL denselben L-Wert haben, aber Gelb wirkt trotzdem viel heller als Blau.


Warum ist das so?

HSL basiert letztlich auf dem üblichen RGB-Farbraum. Dieser ist praktisch für Bildschirme, aber nicht wahrnehmungslinear.

Das heißt:

Insbesondere ist unser Auge für verschiedene Farbbereiche unterschiedlich empfindlich:


Was man stattdessen braucht

Wenn du Farben wirklich gleich hell machen willst, brauchst du einen perzeptuell gleichmäßigeren Farbraum. Dafür gibt es mathematische Modelle.

Die wichtigsten sind:

  1. CIELAB / L*a*b*

    • L* steht näher an der wahrgenommenen Helligkeit
    • deutlich besser als HSL
  2. CIELUV

    • ähnlicher Zweck, etwas anderer Schwerpunkt
  3. OKLab / OKLCH

    • moderner
    • für UI, Web und Design oft sehr gut geeignet
    • in vielen Fällen heute die praktisch beste Wahl

Die eigentliche Idee: „Luminanz“ und „perzeptuelle Helligkeit“

Es gibt dabei zwei verwandte, aber verschiedene Dinge:

1. Physikalische bzw. technische Helligkeit: relative Luminanz

Für einen sRGB-Farbwert berechnet man zunächst die relative Luminanz $Y$.

Wenn R, G, B im Bereich 0 bis 1 als lineare RGB-Werte vorliegen, dann gilt:

$$
Y = 0.2126 R + 0.7152 G + 0.0722 B
$$

Wichtig: Das sind lineare RGB-Werte, nicht die direkt aus CSS bekannten 8-Bit-sRGB-Werte.

Vorher muss man sRGB erst linearisieren. Für einen sRGB-Kanal $c_{srgb}$ gilt:

$$
c_{lin} =
\begin{cases}
\frac{c_{srgb}}{12.92}, & c_{srgb} \le 0.04045 \
\left(\frac{c_{srgb}+0.055}{1.055}\right)^{2.4}, & c_{srgb} > 0.04045
\end{cases}
$$

Diese Luminanz ist z. B. wichtig für:

Aber: gleiche Luminanz ist noch nicht perfekt dasselbe wie „gleich hell empfunden“.


2. Wahrgenommene Helligkeit: perzeptuelle Lightness

Dafür gibt es z. B. in CIELAB die Größe $L^*$.

Aus der relativen Luminanz $Y$ und dem Referenz-Weiß $Y_n$ wird näherungsweise berechnet:

$$
L^* = 116 \cdot \left(\frac{Y}{Y_n}\right)^{1/3} - 16
$$

für größere Werte; genauer ist die Definition stückweise, aber die Grundidee ist:

Gleiche $L^*$-Werte sind viel näher an „gleich hell“ als gleiche HSL-L-Werte.


Für CSS heute besonders interessant: OKLCH

Für praktische Arbeit im Web ist OKLCH oft die beste Antwort.

OKLCH besteht aus:

Das ist also ähnlich intuitiv wie HSL, aber viel besser an die Wahrnehmung angepasst.

Vorteil

Wenn du in OKLCH den gleichen L-Wert verwendest, wirken Farben wesentlich eher gleich hell.

Beispiel in CSS:

color: oklch(0.7 0.15 30);
color: oklch(0.7 0.15 120);
color: oklch(0.7 0.15 260);

Hier ist die Helligkeit durch 0.7 deutlich konsistenter als bei vergleichbaren HSL-Farben.


Also: Gibt es eine mathematische Rechenweise?

Ja. Sogar mehrere — je nachdem, was du mit „hell“ meinst:

  1. Gleiche technische Luminanz

    • über lineares RGB und
      $$
      Y = 0.2126 R + 0.7152 G + 0.0722 B
      $$
    • gut für Kontrast und Messbarkeit
  2. Gleiche wahrgenommene Helligkeit

    • über CIELAB L* oder besser OKLab/OKLCH L
    • gut für Design, UI und Farbpaletten

Praktische Empfehlung

Wenn dein Ziel ist:

„Farben sollen im Interface gleich hell wirken“

Dann nimm OKLCH statt HSL.

„Ich will Kontraste mathematisch korrekt prüfen“

Dann arbeite mit relativer Luminanz.

„Ich will ein älteres, etabliertes Farbmodell“

Dann ist CIELAB eine gute Wahl.


Warum HSL trotzdem oft benutzt wird

HSL ist beliebt, weil es sehr intuitiv aussieht:

Aber dieses L ist eher ein Modellparameter als echte visuelle Helligkeit.

Man kann HSL gut verwenden für:

Aber nicht, wenn du exakt gleich helle Farben willst.


Kurzfassung

Ja, dafür gibt es mathematische Modelle.
Wenn Farben wirklich gleich hell wirken sollen, ist HSL ungeeignet.

Am besten nimmst du:

Wenn du möchtest, kann ich dir als Nächstes noch zeigen:

  1. warum HSL-L mathematisch so definiert ist,
  2. wie man eine HSL-Farbe in relative Luminanz umrechnet, oder
  3. wie man in CSS mit OKLCH eine gleichhelle Farbpalette baut.
OKLCH

OKLCH - SCSS/SASS - Dark/Light Mode

Wenn deine Farben bereits als oklch(...) vorliegen, kannst du eine Funktion bauen, die einfach den Lightness-Wert umkehrt:

$$
L_{\text{neu}} = 1 - L
$$

Also aus:

oklch(0.3 0.15 30)

wird:

oklch(0.7 0.15 30)

Einfache Sass-Funktion

@use "sass:color";
@use "sass:math";

@function invert-oklch-lightness($c) {
  $space: color.space($c);

  @if $space != oklch {
    @error "invert-oklch-lightness erwartet eine OKLCH-Farbe, bekommen: #{$space}";
  }

  $l: color.channel($c, "lightness", $space: oklch);
  $chroma: color.channel($c, "chroma", $space: oklch);
  $hue: color.channel($c, "hue", $space: oklch);

  $new-l: 1 - $l;

  @return oklch($new-l $chroma $hue);
}

Verwendung

$color: oklch(0.3 0.15 30);
$dark-mode: invert-oklch-lightness($color);

Ergebnis:

oklch(0.7 0.15 30)

Beispiel im Einsatz

@use "sass:color";

@function invert-oklch-lightness($c) {
  @if color.space($c) != oklch {
    @error "Nur OKLCH-Farben sind erlaubt.";
  }

  $l: color.channel($c, "lightness", $space: oklch);
  $chroma: color.channel($c, "chroma", $space: oklch);
  $hue: color.channel($c, "hue", $space: oklch);

  @return oklch(1 - $l $chroma $hue);
}

$bg-light: oklch(0.92 0.03 240);
$bg-dark: invert-oklch-lightness($bg-light);

$accent-light: oklch(0.3 0.15 30);
$accent-dark: invert-oklch-lightness($accent-light);

:root {
  --bg: #{$bg-light};
  --accent: #{$accent-light};
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #{$bg-dark};
    --accent: #{$accent-dark};
  }
}

Wichtiger Hinweis

Das ist mathematisch simpel, aber gestalterisch nicht immer optimal

Denn ein „guter“ Dark-Mode ist oft nicht nur:

  1. Lightness invertieren
  2. Chroma gleich lassen
  3. Hue gleich lassen

Sondern häufig will man zusätzlich:


Etwas robustere Variante

Wenn du willst, kannst du beim Invertieren gleichzeitig die Chroma leicht dämpfen:

@use "sass:color";
@use "sass:math";

@function dark-mode-oklch($c, $chroma-factor: 0.85) {
  @if color.space($c) != oklch {
    @error "Nur OKLCH-Farben sind erlaubt.";
  }

  $l: color.channel($c, "lightness", $space: oklch);
  $chroma: color.channel($c, "chroma", $space: oklch);
  $hue: color.channel($c, "hue", $space: oklch);

  $new-l: 1 - $l;
  $new-chroma: $chroma * $chroma-factor;

  @return oklch($new-l $new-chroma $hue);
}

Beispiel:

$color: oklch(0.3 0.15 30);
$dark: dark-mode-oklch($color);

Ergibt ungefähr:

oklch(0.7 0.1275 30)

Falls du nur rohe Werte übergeben willst

Wenn du lieber direkt mit Zahlen statt mit einem Farbwert arbeitest:

@function invert-oklch($l, $c, $h) {
  @return oklch(1 - $l $c $h);
}

Verwendung:

$color: invert-oklch(0.3, 0.15, 30);

Fazit

Ja, absolut.
Für deinen konkreten Fall reicht im Prinzip genau das:

@function invert-oklch-lightness($c) {
  $l: color.channel($c, "lightness", $space: oklch);
  $chroma: color.channel($c, "chroma", $space: oklch);
  $hue: color.channel($c, "hue", $space: oklch);

  @return oklch(1 - $l $chroma $hue);
}

Wenn du möchtest, kann ich dir als Nächstes auch noch eine Version schreiben, die:

  1. auch normale CSS-Strings wie oklch(0.3 0.15 30) verarbeitet, oder
  2. eine ganze Sass-Map mit Theme-Farben automatisch in Dark-Mode-Werte umwandelt.
OKLCH

HSL lightness is not true perceived brightness 🎨

The L value in HSL is only a geometric quantity within that color model. It is not defined so that colors with the same L are perceived by humans as equally bright.

A typical example:

  1. a strong yellow
  2. a strong blue

can have the same HSL L value, yet yellow will still appear much brighter than blue.

Why is that?

HSL is ultimately based on the usual RGB color space. RGB is practical for displays, but it is not perceptually uniform.

That means:

  1. equal numerical changes in RGB or HSL
  2. do not produce equally large visual changes

In particular, our eyes are not equally sensitive across all hues:

  1. yellow/green often appears very bright
  2. blue often appears much darker
  3. even when the technical values look similar

What you need instead

If you want colors to be truly equally bright, you need a more perceptually uniform color space. There are mathematical models for that.

The most important ones are:

  1. CIELAB / L*a*b*

    1. L* is much closer to perceived lightness
    2. much better than HSL
  2. CIELUV

    1. similar purpose
    2. slightly different focus
  3. OKLab / OKLCH

    1. more modern
    2. often very well suited for UI, web, and design work
    3. in many cases, the best practical choice today

The core idea: “luminance” vs. “perceived lightness”

These are related, but not the same thing.

1. Physical or technical brightness: relative luminance

For an sRGB color, you can first compute the relative luminance $Y$.

If R, G, and B are linear RGB values in the range from 0 to 1, then:

$$
Y = 0.2126 R + 0.7152 G + 0.0722 B
$$

Important: these are linear RGB values, not the raw 8-bit sRGB values you typically use in CSS.

So first, sRGB must be linearized. For an sRGB channel $c_{srgb}$:

$$
c_{lin} =
\begin{cases}
\frac{c_{srgb}}{12.92}, & c_{srgb} \le 0.04045 \
\left(\frac{c_{srgb}+0.055}{1.055}\right)^{2.4}, & c_{srgb} > 0.04045
\end{cases}
$$

This luminance is important for things like:

  1. contrast calculations
  2. WCAG
  3. technical comparisons of brightness

But equal luminance is still not perfectly the same as “equally bright as perceived.”

2. Perceived brightness: perceptual lightness

For this, CIELAB uses the quantity $L^*$.

From the relative luminance $Y$ and the reference white $Y_n$, it is approximately calculated as:

$$
L^* = 116 \cdot \left(\frac{Y}{Y_n}\right)^{1/3} - 16
$$

for larger values; more precisely, the full definition is piecewise. But the main idea is:

  1. it is not linear
  2. it is adjusted to human perception

Equal $L^*$ values are much closer to “equally bright” than equal HSL L values.

Especially relevant for CSS today: OKLCH

For practical work on the web, OKLCH is often the best answer.

OKLCH consists of:

  1. L = perceived lightness
  2. C = chroma / colorfulness
  3. H = hue angle

So it is similar to HSL in terms of intuition, but much better aligned with perception.

Advantage

If you use the same L value in OKLCH, colors are much more likely to appear equally bright.

Example in CSS:

color: oklch(0.7 0.15 30);
color: oklch(0.7 0.15 120);
color: oklch(0.7 0.15 260);

Here, the lightness set by 0.7 is much more consistent than in comparable HSL colors.

So: is there a mathematical way to do this?

Yes. In fact, there are several—depending on what exactly you mean by “equally bright”:

  1. Equal technical luminance

    1. using linear RGB and
      $$
      Y = 0.2126 R + 0.7152 G + 0.0722 B
      $$
    2. good for contrast and measurable brightness
  2. Equal perceived lightness

    1. using CIELAB L* or, better, OKLab/OKLCH L
    2. good for design, UI, and color palettes

Practical recommendation

If your goal is:

“Colors should appear equally bright in an interface”

Use OKLCH instead of HSL.

“I want mathematically correct contrast checks”

Work with relative luminance.

“I want an older, well-established color model”

Then CIELAB is a good choice.

Why HSL is still often used

  1. H = hue
  2. S = saturation
  3. L = light/dark

But that L is more of a model parameter than a true measure of visual lightness.

HSL can still be useful for:

  1. simple color variations
  2. quick prototypes
  3. small adjustments

But not if you want colors that are precisely equal in perceived brightness.

Short version

Yes, there are mathematical models for this.
If colors should really appear equally bright, HSL is not suitable.

The best options are:

  1. OKLCH for practical CSS and UI work
  2. CIELAB / OKLab if you want to work in perceptually meaningful color spaces more rigorously

If you want, I can also show you next:

  1. why HSL lightness is defined the way it is
  2. how to convert an HSL color into relative luminance
  3. how to build an equal-lightness color palette in CSS using OKLCH
OKLCH

Ways to define colors in CSS — from HEX to OKLCH 🎨

CSS has accumulated quite a few ways to describe color over the years. Some are familiar and compact, like #ff0000; some are practical and expressive, like rgb(255 0 0 / 0.5); and some are modern, powerful, and much better aligned with human perception—especially oklch().

If you write CSS today, it’s worth understanding not just how these formats work, but when each one makes sense. This article walks through the main color syntaxes in CSS, explains their strengths and trade-offs, and gives special attention to OKLCH, which is becoming one of the most useful ways to work with color in modern design systems.

Why CSS has so many color formats

Different color formats exist because they solve different problems:

  1. Convenience

    1. Named colors like red are easy to remember.
    2. HEX is compact and common in design tools.
  2. Control

    1. rgb() and hsl() make it easier to think in channels.
    2. Alpha transparency can be expressed clearly.
  3. Color science

    1. Newer spaces like lab(), lch(), oklab(), and oklch() aim to make color adjustments more perceptually meaningful.
    2. They help produce more consistent palettes, gradients, and theme systems.

In short: older formats are still useful, but newer ones are often better for design work that needs consistency and nuance.

Named colors

CSS supports a long list of predefined color keywords:

color: red;
background: rebeccapurple;
border-color: lightgray;

These are readable and quick, but they have limits:

  1. They cover only a fixed set of colors.
  2. They aren’t precise enough for most branding or system design.
  3. Their perceived brightness and saturation vary widely.

Named colors are nice for demos, quick experiments, and a few memorable values like white, black, transparent, or rebeccapurple. For serious visual design, developers usually move to numeric color functions.

HEX colors

HEX is one of the most recognizable CSS color formats:

color: #ff0000;
color: #f00;

These two values mean the same thing: pure red.

How HEX works

A 6-digit HEX color is structured like this:

#RRGGBB

Each pair is a channel from 00 to ff:

  1. RR = red
  2. GG = green
  3. BB = blue

So:

#ff0000 /* red */
#00ff00 /* green */
#0000ff /* blue */
#ffffff /* white */
#000000 /* black */

There is also shorthand:

#f00   /* equivalent to #ff0000 */
#0f0   /* equivalent to #00ff00 */
#fff   /* equivalent to #ffffff */

And alpha can be included too:

#ff000080 /* red at about 50% opacity */
#f008     /* shorthand with alpha */

When HEX is useful

  1. Compact
  2. Widely recognized
  3. Easy to copy from design tools

But it also has some drawbacks:

  1. It’s not very intuitive for humans.

    1. Looking at #7a5ccf doesn’t tell you much immediately.
  2. Adjusting colors manually is awkward.

    1. Making something “a bit lighter” is not straightforward.
  3. It reflects RGB encoding rather than human perception.

    1. Equal numeric changes do not feel like equal visual changes.

HEX is still common and totally valid, but it’s often best thought of as a storage or interchange format rather than the most ergonomic design format.

RGB and RGBA

RGB expresses colors through red, green, and blue channels:

color: rgb(255 0 0);
color: rgb(255, 0, 0);

Both forms are understood in CSS, though the space-separated modern syntax is generally preferred.

You can also include alpha:

color: rgb(255 0 0 / 50%);
color: rgb(255 0 0 / 0.5);

Why RGB is useful

RGB maps directly to how screens emit light, so it is fundamental in digital color. It’s often useful when:

  1. You need direct channel control.
  2. You want explicit alpha handling.
  3. You are working with JavaScript, canvas, or generated colors.

Example:

background-color: rgb(34 197 94 / 0.2);

That said, RGB has the same conceptual limitation as HEX: it is not a perceptual color space. Two colors with similar channel differences may not look similarly different.

HSL and HSLA

HSL stands for Hue, Saturation, Lightness:

color: hsl(0 100% 50%);
color: hsl(240 100% 50%);
color: hsl(120 100% 25% / 0.8);

Many developers like HSL because it feels more intuitive than RGB.

What the parts mean

  1. Hue

    1. The basic position on the color wheel, usually in degrees.
    2. 0 is red, 120 is green, 240 is blue.
  2. Saturation

    1. How vivid or grayish the color is.
    2. 0% is gray, higher values are more colorful.
  3. Lightness

    1. How light or dark the color appears.
    2. 0% is black, 100% is white.

Why HSL became popular

HSL is often easier to reason about when making quick adjustments:

/* same hue, different lightness */
--blue-40: hsl(220 80% 40%);
--blue-50: hsl(220 80% 50%);
--blue-60: hsl(220 80% 60%);

This looks appealing, but there is a catch: HSL lightness is not perceptually uniform. Colors with the same HSL lightness often do not appear equally bright. Yellow, for example, tends to look much brighter than blue at the same nominal lightness.

So HSL is intuitive, but it can be misleading when building balanced palettes.

HWB

CSS also supports hwb(), which means Hue, Whiteness, Blackness:

color: hwb(200 20% 10%);

This format is less common, but it can feel natural because it describes a hue mixed with white and black. It is useful in certain tooling and algorithmic color generation, though it has not become as mainstream as HEX, RGB, or HSL.

For many authors, hwb() is more of a nice-to-know syntax than an everyday tool.

Lab and LCH

As CSS evolved, it began to support more perceptually oriented color spaces.

lab()

lab() is based on the CIE Lab color space:

color: lab(60% 30 20);

It uses:

  1. L for lightness
  2. a for the green–red axis
  3. b for the blue–yellow axis

lch()

lch() is a cylindrical representation of Lab:

color: lch(60% 50 30);

It uses:

  1. L for lightness
  2. C for chroma
  3. H for hue

This already feels more designer-friendly than lab(), because chroma and hue are easier to reason about than a and b.

Lab and LCH were important steps toward more perceptual CSS color handling, but in practice, OKLab and OKLCH are often even better choices.

OKLab and OKLCH — the modern highlight ✨

If there is one color format worth learning now, it is oklch().

Why OKLCH matters

OKLCH is built on OKLab, a color space designed to be more perceptually uniform than older RGB/HSL-style approaches and often more practical than CIE Lab/LCH for interface and web design.

The big promise is simple:

If you change a value in OKLCH, the visual result is more likely to match what you expected.

This is incredibly helpful for:

  1. Building color scales
  2. Creating themes
  3. Keeping brightness consistent across hues
  4. Producing smoother gradients
  5. Making systematic adjustments with less guesswork

The structure of oklch()

An OKLCH color looks like this:

color: oklch(62% 0.18 264);

The three main components are:

  1. Lightness

    1. Controls how light or dark the color appears.
    2. Usually the easiest value to tune for contrast and hierarchy.
  2. Chroma

    1. Controls color intensity.
    2. Roughly similar to saturation, but more grounded in the color space.
    3. Higher values are more vivid, though the achievable maximum depends on hue.
  3. Hue

    1. Controls the color family.
    2. Expressed as an angle.

Alpha can be added too:

color: oklch(62% 0.18 264 / 0.7);

Why designers and developers like OKLCH

1. Lightness behaves more sensibly

With HSL, equal lightness values across different hues often look inconsistent. In OKLCH, lightness is much closer to perceived lightness.

That means this kind of palette is more reliable:

--purple-40: oklch(40% 0.16 300);
--purple-55: oklch(55% 0.16 300);
--purple-70: oklch(70% 0.16 300);

Changing the first number tends to produce a more predictable visual ramp.

2. Chroma is better than “saturation” for many tasks

“Saturation” in HSL can be deceptive. A color at 100% saturation is not necessarily the most vivid or the most balanced version of that hue in practice.

In OKLCH, chroma is a more useful measure of colorfulness. If you reduce chroma, colors generally become more muted in a way that feels more natural:

--brand-strong: oklch(60% 0.22 250);
--brand-soft:   oklch(60% 0.10 250);

These are easier to think about as “same lightness, same hue, different intensity.”

3. Hue shifts are easier to manage

Because OKLCH is designed for perceptual consistency, rotating hue while holding other values steady often gives more balanced results than in older spaces.

This is valuable for generating semantic palettes:

--info:    oklch(65% 0.14 240);
--success: oklch(65% 0.14 145);
--warning: oklch(65% 0.14 85);
--danger:  oklch(65% 0.14 25);

These colors are not magically perfect, but they often start from a much better baseline.

Reading OKLCH intuitively

A useful way to think about it is:

oklch(lightness chroma hue)

For example:

oklch(70% 0.12 210)

can be read as:

  1. A fairly light color
  2. With moderate intensity
  3. In the blue-cyan range

Over time, this becomes surprisingly ergonomic.

Practical examples

A button palette

:root {
  --button-bg: oklch(62% 0.17 260);
  --button-bg-hover: oklch(56% 0.17 260);
  --button-text: oklch(98% 0.01 260);
}

This works well because:

  1. Hover is created mainly by lowering lightness.
  2. Hue and chroma stay stable.
  3. The relationship between states remains coherent.

A muted surface system

:root {
  --surface-1: oklch(99% 0.01 250);
  --surface-2: oklch(96% 0.01 250);
  --surface-3: oklch(92% 0.02 250);
  --text-1: oklch(22% 0.02 250);
  --text-2: oklch(38% 0.02 250);
}

Notice how even “neutral” grays can carry a slight hue bias. That can make an interface feel warmer, cooler, softer, or more branded without becoming obviously colored.

Important caveat: gamut limits

OKLCH is excellent, but not every combination of lightness, chroma, and hue can be displayed on a typical screen. In other words, some values fall outside the available gamut.

This matters because:

  1. Very high chroma may not be possible for certain hues.
  2. The browser may clamp or adjust out-of-range values.
  3. A color that is mathematically valid may not render as expected on all devices.

So while OKLCH is powerful, it still has real-world limits. A practical workflow is to:

  1. Start with reasonable chroma values
  2. Preview in actual browsers
  3. Use tooling that can warn about gamut issues

Alpha and transparency

Most modern CSS color functions support alpha using slash syntax:

rgb(0 0 0 / 0.5)
hsl(220 20% 20% / 0.3)
oklch(60% 0.15 240 / 0.4)

This is cleaner than older function pairs like rgba() and hsla(). While those names still exist historically, modern CSS treats alpha as a natural part of the same color function.

That consistency is one of the nice improvements in modern CSS syntax.

Choosing the right format

There is no single correct format for every situation. A sensible rule of thumb is:

  1. Named colors

    1. Good for simple cases and quick demos
  2. HEX

    1. Good for compatibility, compactness, and copy-paste from design tools
  3. RGB

    1. Good when you want direct channel control or explicit programmatic handling
  4. HSL

    1. Good for quick human-readable adjustments
    2. Less reliable for perceptual consistency
  5. Lab/LCH

    1. Good for more advanced perceptual workflows
    2. Often overshadowed by OKLab/OKLCH for modern UI work
  6. OKLCH

    1. Excellent for design systems, palettes, theming, and more visually consistent adjustments
    2. Often the best modern choice when browser support fits your audience

A few practical recommendations

If you’re building modern CSS today, here’s a pragmatic approach:

  1. Use OKLCH when you are defining a system of colors.

    1. Especially for scales, semantic tokens, interactive states, and theme variants.
  2. Use HEX or RGB when integration or tooling makes them more convenient.

    1. For example, some APIs, older docs, or design exports may still rely on them.
  3. Be cautious with HSL for palette construction.

    1. It is intuitive, but its “lightness” can mislead you.
  4. Always test actual contrast and rendering.

    1. No color space replaces accessibility checks.
    2. Perceptual consistency does not automatically guarantee sufficient contrast.

Final thoughts

CSS color has evolved from a handful of convenient notations into a genuinely rich system. HEX, RGB, and HSL are still useful and widely relevant, but they reflect older ways of thinking about color—more tied to encoding or simplified mental models than to human perception.

OKLCH stands out because it bridges technical precision and design intuition. It gives you a way to adjust lightness, intensity, and hue in a manner that much more closely matches what your eyes expect to see. For modern UI work, that makes it one of the most compelling color formats CSS has ever offered.

If you only take one thing away, let it be this: learn oklch(). Even if you continue using HEX or RGB in some places, understanding OKLCH will make you better at choosing, adjusting, and organizing color across the board.

Dark Mode

Dark Mode

Dark Mode: Gängige Methoden

1. Über prefers-color-scheme automatisch dem System folgen

Der Browser erkennt, ob das Betriebssystem auf Hell oder Dunkel gestellt ist.

Beispiel

body {
  background: white;
  color: black;
}

@media (prefers-color-scheme: dark) {
  body {
    background: #121212;
    color: white;
  }
}

Vorteile

Nachteile


2. Per CSS-Klasse auf html oder body

Du setzt z. B. eine Klasse wie dark auf das <html>- oder <body>-Element und definierst dafür eigene Styles.

Beispiel

<html class="dark">
body {
  background: white;
  color: black;
}

.dark body {
  background: #121212;
  color: white;
}

Vorteile

Nachteile


3. Mit CSS Custom Properties (Variablen)

Oft die sauberste Lösung: Du definierst Farbvariablen und überschreibst sie im Dark Mode.

Beispiel

:root {
  --bg: white;
  --text: black;
}

.dark {
  --bg: #121212;
  --text: white;
}

body {
  background: var(--bg);
  color: var(--text);
}

Vorteile

Nachteile


4. Kombination aus Systempräferenz + manuellem Toggle

Das ist in der Praxis oft die beste Lösung.

Typischer Ablauf

Beispielidee

Vorteile

Nachteile


5. Framework-spezifische Lösungen

Viele Frameworks bringen eigene Patterns mit:

Vorteil

Nachteil


Technisch gängige Umsetzungsmuster

A. Nur CSS

Gut für einfache Websites:

B. CSS + JavaScript Toggle

Gut für die meisten Websites:

C. Design Tokens / Theming-System

Gut für große Anwendungen:


Worauf man achten sollte

1. Nicht nur Hintergrund und Text ändern

Auch wichtig:

2. Kontrast

Dark Mode ist nicht einfach „schwarz mit weißem Text“.
Besser:

3. Bilder und Logos

4. FOUC / Flash beim Laden vermeiden

Wenn das Theme erst nach dem Laden per JS gesetzt wird, sieht man kurz das falsche Theme.
Lösung:

5. Browser-UI anpassen

Mit

<meta name="color-scheme" content="light dark">

oder in CSS:

:root {
  color-scheme: light dark;
}

können native Form-Controls und Scrollbars besser zum Theme passen.


Empfehlung für die Praxis

Für moderne Websites ist meistens diese Kombination am sinnvollsten:

Das ist heute wahrscheinlich der gängigste und robusteste Ansatz.

shadcn/ui

shadcn/ui

ui.shadcn.com: Ein ausführlicher Leitfaden zu shadcn/ui

shadcn/ui — erreichbar über ui.shadcn.com — gehört zu den einflussreichsten Werkzeugen im modernen React- und Tailwind-Ökosystem. Es wird oft als Komponentenbibliothek bezeichnet, unterscheidet sich aber grundlegend von klassischen UI-Libraries wie Material UI, Ant Design oder Chakra UI.

Der wichtigste Gedanke lautet:

shadcn/ui installiert keine fremde Design-Bibliothek in das Projekt. Es bringt den Quellcode der gewünschten Komponenten direkt in das eigene Repository.

Diese Idee verändert, wie Teams Komponenten auswählen, gestalten, warten und langfristig kontrollieren. Statt eine Black Box mit vorgegebenen Abstraktionen zu verwenden, erhält man zugänglichen, anpassbaren Code — typischerweise auf Basis von React, Tailwind CSS, Radix UI und TypeScript.


Was ist shadcn/ui?

shadcn/ui ist eine Sammlung hochwertiger, zugänglicher und anpassbarer UI-Komponenten sowie zugehöriger Werkzeuge. Die Website bietet unter anderem:

Die Komponenten richten sich vor allem an Projekte mit:

Inzwischen existieren außerdem Integrations- und Anpassungsmöglichkeiten für weitere Frameworks und Setups. Die konkrete Auswahl sollte jedoch stets anhand der aktuellen Dokumentation auf ui.shadcn.com geprüft werden, da das Ökosystem aktiv weiterentwickelt wird.


Die zentrale Besonderheit: Kein klassisches npm-UI-Paket

Bei einer traditionellen Komponentenbibliothek sieht die Nutzung häufig so aus:

import { Button } from "some-ui-library"

export function SaveButton() {
  return <Button variant="primary">Speichern</Button>
}

Das ist komfortabel, hat aber Konsequenzen:

Bei shadcn/ui erfolgt der Ablauf anders. Man fügt beispielsweise einen Button über die CLI hinzu:

npx shadcn@latest add button

Danach liegt der Komponentenquellcode im eigenen Projekt, etwa in einer Struktur wie:

components/
└── ui/
    └── button.tsx

Die Anwendung importiert anschließend die eigene lokale Datei:

import { Button } from "@/components/ui/button"

export function SaveButton() {
  return <Button>Speichern</Button>
}

Die Datei gehört nun zum Projekt. Entwicklerinnen und Entwickler können sie:

Das ist der Kern des Konzepts.


„Open Code“ statt „Open Source Library“

shadcn/ui wird häufig mit dem Ausdruck Open Code verbunden. Damit ist nicht nur gemeint, dass Quellcode sichtbar ist. Gemeint ist vielmehr ein bestimmtes Nutzungsmodell:

  1. Eine Komponente wird aus einem bekannten, gepflegten Ausgangspunkt übernommen.
  2. Der Code wird direkt im eigenen Repository abgelegt.
  3. Das Team übernimmt bewusst Verantwortung für diesen Code.
  4. Das Design-System entwickelt sich gemeinsam mit der Anwendung weiter.

Eine klassische Library bietet meist eine API, die man konsumiert. shadcn/ui liefert dagegen eher ein ausgearbeitetes Startmaterial.

Diese Philosophie passt gut zu Teams, die ihr Interface als Teil ihres Produkts verstehen — nicht als austauschbare Darstellungsschicht.


Die technische Grundlage

shadcn/ui setzt nicht auf eine einzige technische Abstraktion. Vielmehr kombiniert es mehrere etablierte Werkzeuge.

React als Komponentenmodell

Die meisten shadcn/ui-Komponenten werden als React-Komponenten bereitgestellt. Das macht sie besonders geeignet für moderne Web-Anwendungen mit:

Die Komponenten sind gewöhnlich klein genug, um nachvollziehbar zu bleiben, aber vollständig genug, um produktive Anwendungsfälle abzudecken.


Tailwind CSS für Styling

Das visuelle Styling basiert in der Regel auf Tailwind CSS. Statt große CSS-Dateien mit komplexer Selektorspezifität zu pflegen, werden Utility-Klassen direkt an Komponenten verwendet.

Ein vereinfachtes Beispiel:

<button
  className="
    inline-flex items-center justify-center rounded-md
    bg-primary px-4 py-2 text-sm font-medium text-primary-foreground
    transition-colors hover:bg-primary/90
    focus-visible:outline-none focus-visible:ring-2
    disabled:pointer-events-none disabled:opacity-50
  "
>
  Speichern
</button>

Die tatsächlichen Komponenten enthalten häufig zusätzliche Logik, etwa für Varianten, Größen, Zustände und Barrierefreiheit. Der grundsätzliche Stil bleibt jedoch sichtbar und nachvollziehbar.

Vorteile von Tailwind in diesem Kontext

Mögliche Nachteile

shadcn/ui löst dieses Problem nicht automatisch. Es gibt Teams jedoch eine gute Ausgangsstruktur, um konsistente Regeln aufzubauen.


Radix UI für Zugänglichkeit und Interaktion

Viele komplexere Komponenten von shadcn/ui bauen auf Radix UI Primitives auf. Radix bietet sogenannte „headless“ oder ungestylte Bausteine für Interaktionen wie:

„Headless“ bedeutet in diesem Zusammenhang: Die Logik und Zugänglichkeit sind vorhanden, das visuelle Styling wird jedoch nicht vollständig vorgegeben.

Ein Dialog benötigt beispielsweise mehr als nur ein sichtbar eingeblendetes Element. Er muss unter anderem:

Radix UI nimmt bei vielen solchen Problemen einen erheblichen Teil der Arbeit ab. shadcn/ui ergänzt darauf ein konkretes, modernes Design und stellt den vollständigen Integrationscode bereit.


TypeScript für robuste Komponenten-APIs

Die Komponenten sind in der Regel mit TypeScript geschrieben. Das hilft bei:

Ein typischer Button kann beispielsweise sowohl eigene Varianten als auch normale Button-Attribute akzeptieren:

<Button
  variant="destructive"
  size="sm"
  disabled
  onClick={() => {
    console.log("Löschen")
  }}
>
  Löschen
</Button>

Das Ziel ist nicht, eine besonders komplizierte API zu schaffen, sondern eine vertraute, eng an HTML und React orientierte Schnittstelle bereitzustellen.


Das Design-System hinter shadcn/ui

shadcn/ui ist nicht nur eine Ansammlung isolierter Komponenten. Die Komponenten folgen einer gemeinsamen Designsprache. Sie basiert typischerweise auf:

Das Resultat wirkt meist modern, produktiv und eher neutral als stark markenorientiert. Genau das ist eine Stärke: Das Ausgangsdesign ist professionell, ohne die Produktidentität vollständig vorwegzunehmen.


Semantische Design Tokens

Ein zentraler Teil des Systems sind semantische Farbvariablen. Statt überall konkrete Farbwerte zu verwenden, werden Rollen definiert.

Beispielhaft könnte eine Konfiguration so aussehen:

:root {
  --background: 0 0% 100%;
  --foreground: 222.2 47.4% 11.2%;

  --primary: 222.2 47.4% 11.2%;
  --primary-foreground: 210 40% 98%;

  --secondary: 210 40% 96.1%;
  --secondary-foreground: 222.2 47.4% 11.2%;

  --muted: 210 40% 96.1%;
  --muted-foreground: 215.4 16.3% 46.9%;

  --destructive: 0 84.2% 60.2%;
  --destructive-foreground: 210 40% 98%;

  --border: 214.3 31.8% 91.4%;
  --ring: 222.2 84% 4.9%;
}

Die konkrete Syntax kann je nach Setup, Tailwind-Version und shadcn/ui-Konfiguration variieren. Entscheidend ist das Prinzip:

Eine Button-Komponente referenziert dann nicht direkt „blau“ oder „schwarz“, sondern semantische Klassen wie:

className="bg-primary text-primary-foreground"

Dadurch lässt sich das gesamte Erscheinungsbild kontrolliert ändern, ohne jede einzelne Komponente manuell überarbeiten zu müssen.


Dark Mode

Ein großer Vorteil des Token-Ansatzes ist die Unterstützung eines Dark Modes. Statt Komponenten separat umzustylen, werden primär die Variablen der dunklen Umgebung neu definiert.

Vereinfacht:

.dark {
  --background: 222.2 47.4% 11.2%;
  --foreground: 210 40% 98%;

  --primary: 210 40% 98%;
  --primary-foreground: 222.2 47.4% 11.2%;

  --muted: 217.2 32.6% 17.5%;
  --muted-foreground: 215 20.2% 65.1%;

  --border: 217.2 32.6% 17.5%;
}

Wenn Komponenten konsequent semantische Rollen verwenden, reagieren sie automatisch auf den Wechsel zwischen hellem und dunklem Theme.

Das bedeutet jedoch nicht, dass Dark Mode ohne Prüfung fertig ist. Besonders geprüft werden sollten:


Die CLI: Komponenten gezielt in das Projekt übernehmen

Die shadcn-CLI ist eines der wichtigsten Werkzeuge des Projekts. Sie unterstützt bei der Einrichtung und beim Hinzufügen von Komponenten.

Ein typischer Ablauf beginnt mit einer Initialisierung:

npx shadcn@latest init

Je nach Projekt fragt die CLI Konfigurationsdetails ab, etwa:

Anschließend können Komponenten einzeln ergänzt werden:

npx shadcn@latest add button
npx shadcn@latest add dialog
npx shadcn@latest add form
npx shadcn@latest add table

Mehrere Komponenten lassen sich je nach CLI-Version und Konfiguration oft auch gemeinsam hinzufügen:

npx shadcn@latest add button input label textarea

Was die CLI normalerweise erledigt

Beim Hinzufügen einer Komponente kann die CLI unter anderem:

  1. Komponenten-Dateien kopieren oder erzeugen.
  2. Fehlende Paketabhängigkeiten installieren.
  3. Hilfsfunktionen ergänzen.
  4. Importpfade auf die Projektstruktur abstimmen.
  5. Benötigte Primitive oder Utilities berücksichtigen.
  6. Tailwind-kompatible Klassen und Varianten übernehmen.

Die CLI ist damit kein Runtime-System und keine Magie im Browser. Sie ist ein Projektgenerator. Nach dem Generieren ist der erzeugte Code regulärer Projektcode.


Die Konfigurationsdatei

shadcn/ui verwendet typischerweise eine Konfigurationsdatei wie components.json. Sie beschreibt, wie die CLI das Projekt interpretieren soll.

Vereinfacht könnte eine solche Datei so aussehen:

{
  "style": "default",
  "tsx": true,
  "rsc": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "slate",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui"
  }
}

Die tatsächlichen Optionen und Werte können sich weiterentwickeln. Inhaltlich erfüllt die Datei aber eine wichtige Funktion: Sie macht die Generierung reproduzierbar und an die Architektur des jeweiligen Projekts anpassbar.


Die wichtigsten Komponentenarten

ui.shadcn.com stellt zahlreiche Bausteine bereit. Ihre genaue Auswahl kann sich über die Zeit ändern, aber sie lässt sich sinnvoll in Kategorien einteilen.

Grundlegende Eingabe- und Aktionskomponenten

Diese Komponenten bilden die Basis fast jeder Anwendung:

Sie sind besonders nützlich, weil Formulare sehr schnell inkonsistent werden können. Unterschiedliche Rahmenstärken, Höhen, Fokuszustände oder Fehlermeldungen wirken in einer Anwendung sofort unprofessionell.


Overlay- und Dialogkomponenten

Overlays sind interaktiv anspruchsvoll und profitieren besonders stark von zugrunde liegenden Accessibility-Primitives:

Ein Dialog eignet sich beispielsweise für längere Interaktionen, etwa zum Bearbeiten eines Profils. Ein Alert Dialog ist für Entscheidungen mit Folgen gedacht, etwa:

„Möchten Sie dieses Projekt wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.“

Ein Sheet erscheint häufig seitlich am Bildschirmrand und eignet sich für:


Navigationskomponenten

Für den Aufbau strukturierter Anwendungen sind Navigationsmuster zentral:

Die Command-Komponente ist besonders interessant. Sie dient häufig als Grundlage für Command Palettes, also Such- und Aktionsoberflächen ähnlich wie in Entwicklerwerkzeugen oder Produktivitäts-Apps.

Beispiele für mögliche Aktionen:

Projekt öffnen
Neue Rechnung erstellen
Zum Dashboard wechseln
Einstellungen durchsuchen
Teammitglied einladen

In Kombination mit einer Tastenkombination wie ⌘ K oder Ctrl K entsteht ein modernes, schnelles Bedienkonzept.


Informations- und Statuskomponenten

Diese Komponenten helfen, Inhalte übersichtlich darzustellen:

Ein Skeleton verbessert beispielsweise die wahrgenommene Ladezeit. Statt leerer Flächen zeigt die Anwendung eine visuelle Vorschau der noch ladenden Struktur.

<div className="space-y-3">
  <Skeleton className="h-4 w-3/4" />
  <Skeleton className="h-4 w-1/2" />
  <Skeleton className="h-24 w-full" />
</div>

Varianten mit class-variance-authority

Viele shadcn/ui-Komponenten verwenden häufig class-variance-authority, kurz CVA. Dieses Werkzeug hilft dabei, Varianten nicht über verschachtelte Bedingungen im JSX zu verteilen.

Ein vereinfachtes Beispiel für Button-Varianten:

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        destructive:
          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input bg-background hover:bg-accent",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline"
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10"
      }
    },
    defaultVariants: {
      variant: "default",
      size: "default"
    }
  }
)

Die Komponente kann dann mit klaren, typisierten Varianten verwendet werden:

<Button variant="outline" size="sm">
  Abbrechen
</Button>

Das verhindert, dass jede Seite eigene Klassen für Standardfälle erfindet. Gleichzeitig bleibt die gesamte Implementierung lokal und editierbar.


Die Utility cn()

In vielen shadcn/ui-Projekten existiert eine Hilfsfunktion namens cn. Sie kombiniert häufig Werkzeuge wie clsx und tailwind-merge.

Ein mögliches Muster:

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Sie ermöglicht, Standardklassen mit zusätzlichen Klassen zusammenzuführen:

<Card className="border-primary/30 shadow-lg">
  <CardContent>Inhalt</CardContent>
</Card>

Der entscheidende Nutzen liegt darin, dass widersprüchliche Tailwind-Klassen besser behandelt werden können. Wenn eine Komponente etwa standardmäßig p-4 besitzt und der Aufrufer p-8 übergibt, kann tailwind-merge in vielen Fällen die widersprüchliche Klasse sinnvoll auflösen.


Formulare mit shadcn/ui

Formulare gehören zu den häufigsten und anspruchsvollsten Teilen einer Web-Anwendung. Sie verbinden:

shadcn/ui wird häufig zusammen mit React Hook Form und Zod verwendet.

Ein vereinfachtes Schema könnte so aussehen:

import { z } from "zod"

const profileSchema = z.object({
  name: z.string().min(2, {
    message: "Der Name muss mindestens zwei Zeichen enthalten."
  }),
  email: z.string().email({
    message: "Bitte geben Sie eine gültige E-Mail-Adresse ein."
  })
})

Das Formular kann dann Eingabefelder, Labels und Fehlermeldungen konsistent darstellen:

<FormField
  control={form.control}
  name="email"
  render={({ field }) => (
    <FormItem>
      <FormLabel>E-Mail-Adresse</FormLabel>
      <FormControl>
        <Input placeholder="name@beispiel.de" {...field} />
      </FormControl>
      <FormDescription>
        Diese Adresse wird für wichtige Benachrichtigungen verwendet.
      </FormDescription>
      <FormMessage />
    </FormItem>
  )}
/>

Warum diese Kombination praktisch ist

Ohne Struktur entstehen Formulare oft so:

<label>E-Mail</label>
<input />
{error && <span className="text-red-500">{error}</span>}

Das funktioniert für ein Feld, wird aber bei 20 Formularen und mehreren Teams schnell inkonsistent. Mit einem einheitlichen Muster lassen sich:

verlässlicher standardisieren.


Data Tables: leistungsfähig, aber kein Plug-and-Play-Wunder

Ein besonders beliebtes Thema im shadcn/ui-Umfeld sind Daten-Tabellen. Häufig wird dafür TanStack Table verwendet.

Tabellen in professionellen Anwendungen benötigen oft deutlich mehr als eine HTML-Tabelle:

shadcn/ui bietet hierfür eher ein Architektur- und Komponentenbeispiel als eine vollständig vorkonfigurierte Enterprise-Tabelle.

Das ist ein wichtiger Unterschied.

Eine Data Table auf Basis von shadcn/ui kann sehr mächtig werden, aber das Team muss bewusst entscheiden:

Für ein Admin-Dashboard ist diese Flexibilität hervorragend. Für ein kleines Projekt, das nur schnell eine statische Tabelle benötigt, kann sie unnötig komplex sein.


Beispiele, Blocks und Anwendungsoberflächen

Neben einzelnen Komponenten sind auf ui.shadcn.com häufig auch größere Bausteine und Vorlagen zu finden. Dazu können gehören:

Solche Vorlagen sind wertvoll, weil sie nicht nur Einzelteile zeigen, sondern deren Zusammenspiel:

Für viele Teams spart dies erheblich Zeit in der frühen Produktphase.


Das Registry-Konzept

Ein fortgeschrittenes Konzept von shadcn/ui ist das Registry-System. Eine Registry ist vereinfacht eine Quelle, aus der Komponenten, Blöcke, Utilities oder andere Projektbausteine bezogen werden können.

Das kann über die offizielle Registry hinausgehen. Organisationen können beispielsweise interne Komponenten bereitstellen:

Ein Team könnte so eine interne Komponente bereitstellen:

@company/customer-status-card

Und sie projektübergreifend beziehen:

npx shadcn@latest add @company/customer-status-card

Die genaue Syntax und Registry-Konfiguration hängt von der jeweiligen shadcn-Version und Registry-Einrichtung ab. Strategisch ist der Gedanke aber sehr bedeutend:

Ein Unternehmen kann seine eigene Komponentenversorgung aufbauen, ohne zwingend eine umfangreiche, zur Laufzeit eingebundene UI-Library veröffentlichen zu müssen.


Nutzen interner Registries

Interne Registries können insbesondere bei mehreren Anwendungen helfen:

Dabei muss bewusst entschieden werden, wie Updates funktionieren. Da Code in Zielprojekte kopiert wird, gibt es nicht automatisch ein zentrales Runtime-Update wie bei einem npm-Paket.

Das kann Vor- und Nachteile haben.


Updates: Freiheit bedeutet Verantwortung

Ein häufiger Irrtum lautet:

„Wenn ich shadcn/ui nutze, aktualisieren sich meine Komponenten automatisch.“

Das ist gerade nicht das Grundmodell.

Sobald eine Komponente in das eigene Projekt übernommen wurde, ist sie Teil des eigenen Codes. Wenn später eine neue Version der Vorlage, ein Bugfix oder eine Accessibility-Verbesserung erscheint, muss das Team entscheiden:

  1. Ist die Änderung relevant?
  2. Wurde die lokale Komponente bereits angepasst?
  3. Kann der Unterschied übernommen werden?
  4. Gibt es Tests für das bisherige Verhalten?
  5. Entsteht ein Konflikt mit dem eigenen Design-System?

Diese Verantwortung ist nicht grundsätzlich schlecht. Sie entspricht der zentralen Idee von Code Ownership. Sie bedeutet aber, dass shadcn/ui kein „installieren und für immer vergessen“-Werkzeug ist.


Sinnvolle Update-Strategie

Für produktive Anwendungen empfiehlt sich ein bewusster Prozess:

  1. Komponenten nicht unnötig früh forken
    Nur ändern, wenn es einen echten Produkt- oder Designgrund gibt.

  2. Eigene Anpassungen dokumentieren
    Kommentare oder Changelogs helfen, lokale Abweichungen zu verstehen.

  3. Kritische Komponenten testen
    Besonders Dialoge, Menüs, Formulare und Authentifizierungsoberflächen.

  4. Upstream-Änderungen regelmäßig prüfen
    Nicht jede Änderung muss übernommen werden, aber relevante Fixes sollten sichtbar sein.

  5. Design Tokens zentral halten
    Möglichst Farben, Rundungen und Abstände nicht in jeder Komponente individuell verändern.

  6. Kompositionsschicht einführen
    Produkt-spezifische Komponenten wie DeleteProjectDialog sollten über generischen UI-Komponenten liegen.


Empfohlene Projektarchitektur

Eine einfache, gut skalierbare Struktur könnte so aussehen:

app/
  dashboard/
  settings/
  projects/

components/
  ui/
    button.tsx
    card.tsx
    dialog.tsx
    input.tsx
    table.tsx

  layout/
    app-sidebar.tsx
    dashboard-header.tsx

  projects/
    project-card.tsx
    project-status-badge.tsx
    delete-project-dialog.tsx

lib/
  utils.ts
  validations/
    project.ts

hooks/
  use-project-filters.ts

Die Rollen dieser Ebenen

components/ui

Hier liegen die generischen, möglichst produktneutralen shadcn/ui-Bausteine:

Diese Komponenten sollten nur selten fachliche Begriffe wie „Rechnung“, „Kunde“ oder „Projektstatus“ kennen.

components/layout

Diese Ebene enthält wiederkehrende Seitenstrukturen:

Fachliche Komponenten

Hier liegen Komponenten mit Geschäftslogik oder Domänenwissen:

Diese Schicht komponiert die generischen UI-Bausteine zu produktrelevanten Funktionen.


Ein praktisches Beispiel: fachliche Komposition

Statt einen allgemeinen Dialog überall neu zusammenzusetzen, kann eine fachliche Komponente entstehen:

import { Button } from "@/components/ui/button"
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger
} from "@/components/ui/alert-dialog"

type DeleteProjectDialogProps = {
  projectName: string
  onConfirm: () => void
}

export function DeleteProjectDialog({
  projectName,
  onConfirm
}: DeleteProjectDialogProps) {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button variant="destructive">Projekt löschen</Button>
      </AlertDialogTrigger>

      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Projekt wirklich löschen?</AlertDialogTitle>
          <AlertDialogDescription>
            Das Projekt „{projectName}“ wird dauerhaft gelöscht. Diese Aktion
            kann nicht rückgängig gemacht werden.
          </AlertDialogDescription>
        </AlertDialogHeader>

        <AlertDialogFooter>
          <AlertDialogCancel>Abbrechen</AlertDialogCancel>
          <AlertDialogAction onClick={onConfirm}>
            Endgültig löschen
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Die UI-Komponenten bleiben allgemein. Die Domänenkomponente formuliert dagegen die konkrete Produktsprache und das konkrete Verhalten.

Das macht die Anwendung leichter wartbar und übersetzbar.


Zugänglichkeit: Eine starke Grundlage, keine automatische Garantie

shadcn/ui und Radix UI können die Zugänglichkeit deutlich verbessern. Sie liefern oft solide Mechanismen für:

Aber: Zugänglichkeit kann nicht vollständig durch eine Komponentenbibliothek garantiert werden.

Folgende Fehler können trotz guter Komponentenbasis entstehen:

Ein Icon-Button sollte beispielsweise einen klaren Namen besitzen:

<Button variant="ghost" size="icon" aria-label="Benachrichtigungen öffnen">
  <BellIcon />
</Button>

Nicht ausreichend wäre:

<Button variant="ghost" size="icon">
  <BellIcon />
</Button>

Für sehende Nutzerinnen und Nutzer mag das Symbol verständlich sein. Für Screenreader fehlt jedoch die Information, was der Button auslöst.


Vorteile von shadcn/ui

Volle Kontrolle über den Code

Die wichtigste Stärke ist die lokale Eigentümerschaft. Teams können jede Komponente anpassen, ohne auf Erweiterungspunkte einer externen Library warten zu müssen.

Das ist besonders wertvoll bei:


Moderne, hochwertige Ausgangsbasis

Die Komponenten wirken typischerweise:

Dadurch lassen sich MVPs und produktive Anwendungen schnell mit einer glaubwürdigen visuellen Qualität umsetzen.


Gute Kombinierbarkeit

Da shadcn/ui auf bekannten Web- und React-Konzepten basiert, lässt es sich gut mit anderen Werkzeugen verbinden:

shadcn/ui versucht nicht, das gesamte Anwendungsökosystem selbst zu kontrollieren.


Weniger Vendor Lock-in auf Komponentenebene

Wenn eine klassische UI-Library nicht mehr passt, kann ein Wechsel teuer werden. Bei shadcn/ui liegt der relevante Code bereits im Projekt.

Natürlich bleiben Abhängigkeiten wie React, Tailwind oder Radix UI bestehen. Dennoch ist die visuelle und strukturelle Komponentenschicht weniger an eine zentrale UI-Library gebunden.


Gute Lernwirkung

Der Code eignet sich hervorragend, um moderne UI-Patterns zu verstehen:

Für viele Entwicklerinnen und Entwickler ist shadcn/ui deshalb nicht nur ein Werkzeug, sondern auch eine Lernressource.


Nachteile und Grenzen

Kein vollständig automatisiertes Update-Modell

Lokaler Code bringt Verantwortung. Wer ein sehr zentrales, automatisch versioniertes Component Package mit kontrollierten Upgrades sucht, muss eine zusätzliche Strategie aufbauen oder eventuell eine klassische Library bevorzugen.


Tailwind ist praktisch eine Kernannahme

Wer Tailwind CSS nicht einsetzen möchte, wird mit shadcn/ui deutlich weniger Freude haben. Die Komponenten lassen sich zwar umschreiben, aber dann verliert man einen zentralen Vorteil der Ausgangsbasis.


Nicht jede Komponente ist sofort eine Produktlösung

Einige Bausteine sind bewusst Beispiele oder Ausgangspunkte. Besonders bei komplexen Themen wie:

reicht das Kopieren einer Komponente nicht aus. Fachlogik, Performance, Sicherheit und UX müssen weiterhin sorgfältig entwickelt werden.


Konsistenz kann ohne Governance leiden

Die leichte Änderbarkeit kann auch problematisch sein. Wenn jedes Teammitglied Button, Input und Card spontan anders verändert, zerfällt das Design-System.

Hilfreich sind deshalb:


shadcn/ui im Vergleich zu klassischen UI-Libraries

Die folgende Übersicht zeigt die unterschiedlichen Denkmodelle:

Aspekt shadcn/ui Klassische UI-Library
Bereitstellung Komponenten-Code wird ins Projekt übernommen Komponenten werden aus einem Paket importiert
Ownership Team besitzt und pflegt den Code Library-Anbieter pflegt den Kerncode
Anpassbarkeit Sehr hoch Abhängig von Theme- und Override-System
Updates Bewusste Übernahme nötig Paketupdate kann Änderungen bringen
Design-Vorgaben Moderne Basis, leicht anpassbar Häufig stärker durch Library geprägt
Abstraktionsgrad Eher niedrig bis mittel Häufig mittel bis hoch
Einstieg Tailwind- und React-Kenntnisse hilfreich Oft schneller bei Standardfällen
Langfristige Freiheit Hoch Kann stärker an Anbieter-API binden
Konsistenz ohne Regeln Muss intern organisiert werden Wird stärker durch Library erzwungen

Keine der beiden Strategien ist grundsätzlich überlegen.

Eine klassische Library ist oft sinnvoll, wenn:

shadcn/ui ist besonders passend, wenn:


Für welche Projekte eignet sich shadcn/ui?

Sehr gut geeignet für


Mit Bedacht einsetzen bei


Best Practices für produktive Projekte

1. Nicht jede Komponente sofort verändern

Die Standardkomponenten sind bewusst generisch. Es ist oft besser, zuerst mit den vorhandenen Varianten zu arbeiten und Anpassungen erst vorzunehmen, wenn echte Anforderungen bestehen.


2. Tokens vor Einzelklassen ändern

Wenn die Markenfarbe geändert werden soll, ist dies besser über semantische Tokens als über dutzende einzelne bg-blue-*-Klassen zu lösen.

Schlecht:

<Button className="bg-purple-600 hover:bg-purple-700">
  Speichern
</Button>

Besser:

<Button>Speichern</Button>

Und die semantische Primärfarbe zentral definieren.


3. Produktkomponenten über UI-Primitives bauen

Ein Button ist generisch. Ein InviteMemberButton ist fachlich. Diese Trennung hält die Basis wiederverwendbar.

export function InviteMemberButton() {
  return (
    <Button>
      Mitglied einladen
    </Button>
  )
}

Wenn später Berechtigungen, Analytics oder ein Dialog ergänzt werden, bleibt die Logik an einem fachlich passenden Ort.


4. Varianten begrenzen

Es ist verlockend, für jede neue visuelle Idee eine Button-Variante anzulegen:

primary
secondary
subtle
inverse
brand
marketing
premium
warning
highlight
neon

Das führt langfristig zu einem unklaren System. Besser sind wenige semantische Varianten, beispielsweise:

Neue Varianten sollten nur entstehen, wenn sie eine wiederkehrende, klar definierte Bedeutung besitzen.


5. Accessibility und mobile Nutzung testen

Insbesondere bei diesen Komponenten sollte man nicht nur mit der Maus testen:

Ein Mindesttest sollte umfassen:

  1. Bedienung mit Tab und Shift+Tab
  2. Aktivierung mit Enter und Leertaste
  3. Schließen von Overlays mit Escape
  4. Sichtbare Fokuszustände
  5. sinnvolle Screenreader-Beschriftungen
  6. ausreichende Farbkontraste
  7. Bedienbarkeit auf kleinen Bildschirmen

6. Komponenten mit echten Inhalten prüfen

Platzhaltertexte täuschen oft über Layoutprobleme hinweg. Testdaten sollten enthalten:

Ein UI, das nur mit „John Doe“ und „Lorem ipsum“ funktioniert, ist noch nicht produktionsreif.


Fazit

ui.shadcn.com ist weniger eine klassische UI-Bibliothek als ein moderner Baukasten für eigene Komponentenarchitekturen.

Der große Reiz liegt in der Verbindung aus:

shadcn/ui eignet sich besonders für Teams, die nicht nur schnell Oberflächen bauen, sondern langfristig ein kontrollierbares, markenfähiges und wartbares UI-System entwickeln möchten.

Die Kehrseite dieser Freiheit ist Verantwortung: Komponenten müssen bewusst gepflegt, aktualisiert, getestet und architektonisch eingeordnet werden. Wer diese Verantwortung akzeptiert, erhält mit shadcn/ui eine außergewöhnlich starke Grundlage für moderne React-Anwendungen.

shadcn/ui

tweakcn.com: Der visuelle Theme-Editor für shadcn/ui

tweakcn.com ist ein webbasiertes Werkzeug zur visuellen Anpassung von shadcn/ui-Themes. Es richtet sich vor allem an Entwicklerinnen, Entwickler und Design-orientierte Produktteams, die mit shadcn/ui, Tailwind CSS und CSS-Variablen arbeiten.

Die zentrale Aufgabe von tweakcn lautet:

Ein shadcn/ui-Theme visuell konfigurieren, Vorschauen prüfen und die resultierenden Design Tokens als Code in das eigene Projekt übernehmen.

Statt Farbwerte, Border-Radien, Schatten oder Schriftfamilien ausschließlich manuell in CSS-Dateien zu bearbeiten, erlaubt tweakcn eine interaktive Bearbeitung mit unmittelbarer Komponenten-Vorschau.

Es ist damit kein Ersatz für shadcn/ui, sondern eher eine ergänzende Design- und Konfigurationsoberfläche für dessen Token-basiertes Theming.


Kurz erklärt

Mit tweakcn können Teams typischerweise:

Das Tool ist insbesondere nützlich, wenn ein Team nicht beim neutralen Standard-Look von shadcn/ui bleiben möchte, aber auch kein vollständiges individuelles Design-System von Grund auf erstellen will.


Die Beziehung zwischen tweakcn und shadcn/ui

Um tweakcn richtig einzuordnen, ist die Trennung der Rollen wichtig.

shadcn/ui liefert Komponenten

shadcn/ui stellt den Komponenten-Code bereit, zum Beispiel:

Diese Komponenten verwenden in der Regel semantische Tailwind-Klassen und CSS-Variablen, etwa:

<Button className="bg-primary text-primary-foreground">
  Speichern
</Button>

Der Button kennt dabei idealerweise keine konkrete Markenfarbe wie „Blau #2563eb“. Er verwendet vielmehr die semantische Rolle primary.


tweakcn gestaltet die visuellen Tokens

tweakcn greift auf genau diese semantische Token-Struktur auf. Das Tool hilft dabei, die Werte hinter Rollen wie diesen festzulegen:

Vereinfacht gesagt:


Ist tweakcn offiziell Teil von shadcn/ui?

tweakcn sollte nicht automatisch mit der offiziellen Dokumentation oder dem offiziellen CLI-Workflow von shadcn/ui gleichgesetzt werden. Es ist ein ergänzendes Werkzeug im weiteren shadcn/ui-Ökosystem.

Das ist keine Abwertung — im Gegenteil: Solche spezialisierten Tools entstehen häufig gerade deshalb, weil die technische Grundlage offen, gut dokumentiert und weit verbreitet ist.

Trotzdem gilt für produktive Projekte:


Warum ein Tool wie tweakcn sinnvoll ist

Die Theme-Anpassung von shadcn/ui ist technisch nicht besonders schwer, aber sie kann zeitaufwendig und fehleranfällig sein.

Ein klassisches manuelles Vorgehen sieht etwa so aus:

  1. CSS-Datei öffnen.
  2. Variablen anpassen.
  3. Anwendung starten oder neu laden.
  4. Button prüfen.
  5. Card prüfen.
  6. Dialog prüfen.
  7. Dark Mode prüfen.
  8. Kontrast korrigieren.
  9. Wiederholen.

Bei vielen Änderungen entsteht schnell ein Trial-and-Error-Prozess.

tweakcn beschleunigt diesen Ablauf durch eine unmittelbare visuelle Rückmeldung. Statt zuerst abstrakte HSL-Werte zu editieren, sehen Nutzerinnen und Nutzer direkt, wie sich eine Änderung auf typische Oberflächen auswirkt.

Das ist insbesondere bei diesen Fragen hilfreich:


Die Grundlage: Semantische Design Tokens

Das wichtigste Konzept hinter tweakcn sind Design Tokens.

Ein Design Token ist ein wiederverwendbarer, benannter Wert für eine gestalterische Entscheidung. Statt an vielen Stellen konkrete Werte einzutragen, wird eine semantische Rolle definiert.

Beispiel:

:root {
  --primary: 221 83% 53%;
  --primary-foreground: 210 40% 98%;
}

Diese Variablen könnten in Tailwind dann etwa als bg-primary und text-primary-foreground verwendet werden.

Die konkrete Farbe ist hier austauschbar. Ihre Bedeutung bleibt erhalten:

Das ist wesentlich stabiler als überall direkte Klassen zu verwenden:

<button className="bg-blue-600 text-white">
  Speichern
</button>

Bei einer Markenänderung müssten sonst viele Komponenten manuell angepasst werden.


Semantik statt Farbnamen

Ein gutes Theme-System verwendet möglichst wenige fachlich unabhängige Farbnamen wie:

blue
green
purple
gray
red

Stattdessen sind semantische Rollen sinnvoller:

primary
secondary
accent
muted
destructive
background
foreground
border
ring

Der Vorteil ist klar: Eine Marke kann sich visuell verändern, ohne dass die Bedeutung von Komponenten neu definiert werden muss.

Beispiel:

Der Button bleibt in allen Fällen semantisch ein primärer Button.


Helle und dunkle Themes

Ein zentraler Anwendungsfall von tweakcn ist die Arbeit mit Light Mode und Dark Mode.

Ein gutes Dark Theme ist nicht einfach ein invertiertes helles Theme. Es benötigt eigene gestalterische Entscheidungen.

Ein vereinfachtes helles Theme könnte so aussehen:

:root {
  --background: 0 0% 100%;
  --foreground: 222 47% 11%;

  --card: 0 0% 100%;
  --card-foreground: 222 47% 11%;

  --primary: 222 47% 11%;
  --primary-foreground: 210 40% 98%;

  --muted: 210 40% 96%;
  --muted-foreground: 215 16% 47%;

  --border: 214 32% 91%;
  --ring: 222 84% 5%;
}

Ein passendes dunkles Theme benötigt andere Kontraste:

.dark {
  --background: 222 47% 11%;
  --foreground: 210 40% 98%;

  --card: 222 47% 13%;
  --card-foreground: 210 40% 98%;

  --primary: 210 40% 98%;
  --primary-foreground: 222 47% 11%;

  --muted: 217 33% 18%;
  --muted-foreground: 215 20% 65%;

  --border: 217 33% 18%;
  --ring: 213 27% 84%;
}

tweakcn hilft dabei, beide Zustände nebeneinander oder im Wechsel zu prüfen.


Typische Fehler im Dark Mode

Ein Dark Theme wirkt schnell hochwertig, kann aber bei unzureichender Prüfung problematisch werden.

Häufige Fehler sind:

Eine visuelle Vorschau hilft, solche Probleme früh zu erkennen. Sie ersetzt jedoch keine gezielte Accessibility-Prüfung.


Welche Designbereiche lassen sich typischerweise bearbeiten?

Die konkrete Oberfläche und der Funktionsumfang von tweakcn können sich weiterentwickeln. Inhaltlich konzentrieren sich Theme-Editoren für shadcn/ui jedoch auf mehrere wiederkehrende Bereiche.

Farben und Oberflächen

Hier werden semantische Rollen für Hintergründe, Texte und Interaktionen festgelegt:

Die Herausforderung ist nicht nur, schöne Einzelfarben auszuwählen. Entscheidend ist, dass die Rollen zusammen ein kohärentes System bilden.


Typografie

Die Typografie beeinflusst die Produktwahrnehmung erheblich. Sie bestimmt unter anderem:

Ein B2B-Dashboard benötigt oft eine andere Typografie als:

Ein Theme-Editor kann helfen, die Wirkung verschiedener Schriftfamilien und Größenkonzepte im Kontext echter UI-Elemente zu prüfen.

Trotzdem sollte Typografie nicht nur anhand einer Button-Vorschau entschieden werden. Besonders wichtig sind:


Border Radius

Der Border Radius beschreibt, wie stark Ecken abgerundet werden. In shadcn/ui-Setups wird dafür oft ein zentraler Wert wie --radius verwendet.

Beispiel:

:root {
  --radius: 0.625rem;
}

Dieser Wert beeinflusst häufig Cards, Inputs, Buttons, Dialoge und andere Oberflächen.

Die Wirkung kleiner Änderungen wird oft unterschätzt:

Radius-Stil Typische Wirkung
Sehr klein Technisch, präzise, sachlich
Mittel Modern, neutral, vielseitig
Groß Freundlich, weich, konsumorientiert
Sehr groß Stark stilisiert, verspielt oder markant

Ein Radius sollte nicht isoliert gewählt werden. Er muss zu Typografie, Markenbild, Schatten, Abständen und Zielgruppe passen.


Schatten und Tiefenwirkung

Schatten geben Oberflächen Tiefe und trennen Ebenen visuell. Besonders relevant sind sie bei:

Ein zu starker Schatten lässt ein Interface schnell überladen oder altmodisch wirken. Ein zu schwacher Schatten kann dagegen dazu führen, dass Overlays nicht klar vom Hintergrund getrennt sind.

Moderne UI-Systeme verwenden häufig eher subtile Schatten in Kombination mit:


Von tweakcn in ein shadcn/ui-Projekt

Der typische praktische Ablauf sieht ungefähr so aus.

1. Projekt mit shadcn/ui vorbereiten

Zunächst benötigt man ein Projekt mit Tailwind CSS und shadcn/ui oder einer kompatiblen Token-Struktur.

Beispielhaft:

npx shadcn@latest init

Danach existiert meist eine globale CSS-Datei, zum Beispiel:

app/globals.css

oder:

src/index.css

Dort befinden sich häufig die Theme-Variablen.


2. Theme in tweakcn konfigurieren

Im Theme-Editor werden visuelle Entscheidungen getroffen:

Hier sollte nicht nur der primäre Button betrachtet werden. Ein Theme muss auch mit weniger spektakulären Komponenten funktionieren:


3. Theme-Code exportieren oder kopieren

Der Theme-Editor erzeugt typischerweise CSS-Variablen oder vergleichbare Konfigurationswerte.

Diese werden anschließend in die globale CSS-Datei des Projekts übernommen.

Ein möglicher Ausschnitt:

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 224 71% 4%;

    --primary: 243 75% 59%;
    --primary-foreground: 210 40% 98%;

    --secondary: 220 14% 96%;
    --secondary-foreground: 224 71% 4%;

    --muted: 220 14% 96%;
    --muted-foreground: 220 9% 46%;

    --accent: 250 100% 97%;
    --accent-foreground: 243 75% 35%;

    --destructive: 0 84% 60%;
    --destructive-foreground: 210 40% 98%;

    --border: 220 13% 91%;
    --input: 220 13% 91%;
    --ring: 243 75% 59%;

    --radius: 0.625rem;
  }
}

Danach werden die resultierenden Styles im lokalen Projekt getestet.


4. In der realen Anwendung validieren

Die Vorschau in tweakcn ist ein wertvoller Startpunkt. Sie kann aber nicht alle Bedingungen der eigenen Anwendung abbilden.

Nach dem Übernehmen sollten Teams daher zumindest prüfen:


Kontrast und Barrierefreiheit

Ein attraktives Theme ist nicht automatisch barrierefrei.

Besonders bei Markenfarben entsteht oft ein Konflikt: Eine kräftige oder helle Markenfarbe kann im Marketing hervorragend aussehen, aber für normalen Button-Text ungeeignet sein.

Beispiel:

Die richtige Antwort ist nicht immer, die Markenfarbe überall als primary einzusetzen. Es kann sinnvoll sein, zwischen mehreren Rollen zu unterscheiden:


Wichtige Kontrastfälle

Beim Testen eines Themes sollten mindestens diese Kombinationen geprüft werden:

Kombination Warum sie kritisch ist
foreground auf background Standardlesbarkeit der gesamten Oberfläche
primary-foreground auf primary Lesbarkeit primärer Aktionen
muted-foreground auf muted Sekundäre Hinweise dürfen nicht verschwinden
destructive-foreground auf destructive Kritische Aktionen müssen klar lesbar sein
Fokus-Ring auf Hintergrund Tastaturnutzung muss sichtbar bleiben
Input-Text auf Input-Fläche Formulare werden sehr häufig verwendet
Border auf Hintergrund Eingabefelder und Flächen müssen erkennbar bleiben

Für normale Textinhalte wird häufig ein Kontrastverhältnis von mindestens (4.5:1) als wichtige Orientierung verwendet. Große Schrift kann unter bestimmten Bedingungen niedrigere Anforderungen erfüllen, doch für produktive Oberflächen ist ein großzügiger Kontrast meist die robustere Entscheidung.


tweakcn als Werkzeug für Design-Systeme

tweakcn ist nicht nur für Einzelprojekte interessant. Es kann auch im Aufbau eines kleinen oder mittleren Design-Systems hilfreich sein.

Ein Design-System besteht nicht nur aus Komponenten. Es umfasst mehrere Ebenen:

Markenprinzipien
  ↓
Design Tokens
  ↓
Primitive UI-Komponenten
  ↓
Komponentenvarianten
  ↓
Produkt- und Fachkomponenten
  ↓
Seiten und Workflows

tweakcn arbeitet vor allem auf der Ebene der Design Tokens. Es hilft bei den grundlegenden visuellen Entscheidungen, aus denen Komponenten ihre Erscheinung ableiten.


Beispiel einer Token-Hierarchie

Eine mögliche Struktur könnte so aussehen:

Brand color
  ↓
Primary token
  ↓
Primary button
  ↓
Create project action

Oder konkreter:

Markenfarbe: dunkles Indigo
  ↓
--primary: Indigo-Wert
  ↓
<Button variant="default">
  ↓
<CreateProjectButton>

Wenn die Markenfarbe später angepasst wird, ändern sich nicht zwangsläufig Produkttexte oder Komponentenstrukturen. Idealerweise wird nur die zentrale Token-Definition aktualisiert.


Gute Einsatzszenarien

SaaS-Dashboards

SaaS-Anwendungen benötigen oft:

shadcn/ui bietet dafür Komponenten und Patterns. tweakcn hilft, daraus ein markenfähiges, visuell geschlossenes Produkt zu machen.


Interne Unternehmensanwendungen

Interne Tools starten oft pragmatisch und wachsen schnell. Ohne ein Token-System entstehen dann häufig unterschiedliche Grautöne, Button-Stile und Abstände.

Mit einem klaren Theme können Teams früh eine konsistente Basis definieren, ohne sofort ein umfassendes Corporate-Design-System implementieren zu müssen.


MVPs und Start-ups

In der frühen Produktphase sind Geschwindigkeit und Glaubwürdigkeit gleichermaßen wichtig.

Ein Team kann:

  1. shadcn/ui-Komponenten verwenden,
  2. mit tweakcn eine eigene visuelle Richtung wählen,
  3. Tokens in das Projekt übernehmen,
  4. die Anwendung schrittweise weiterentwickeln.

So entsteht schneller ein Produkt, das nicht wie ein unbearbeiteter Standard-Prototyp wirkt.


Rebranding bestehender Anwendungen

Wenn eine Anwendung bereits konsequent auf semantischen Tokens aufgebaut ist, kann ein Rebranding deutlich einfacher werden.

Statt viele Komponenten umzubauen, können Teams zunächst die zentrale Theme-Schicht überarbeiten:

Natürlich müssen danach Screens und kritische Workflows geprüft werden. Dennoch reduziert ein Token-System den Aufwand erheblich.


Grenzen von tweakcn

tweakcn ist ein Produktivitätswerkzeug, kein vollständiger Ersatz für Design, Entwicklung und Qualitätssicherung.

Es ersetzt kein UX-Design

Ein gutes Farbsystem löst keine Probleme wie:

Ein schönes Theme kann ein schlechtes Nutzungskonzept sogar kaschieren, aber nicht lösen.


Es ersetzt kein vollständiges Brand-System

Markenarbeit umfasst mehr als Farben und Radius-Werte:

tweakcn kann ein digitales UI-Theme unterstützen, ist aber keine vollständige Markenplattform.


Es ersetzt keine Accessibility-Prüfung

Eine Vorschau hilft, Kontrastprobleme zu erkennen. Sie garantiert jedoch nicht:

Diese Aspekte müssen im echten Produkt getestet werden.


Es löst keine Architekturprobleme

Wenn eine Anwendung überall direkte Tailwind-Farbklassen nutzt, etwa:

<div className="bg-blue-600 text-white">
  ...
</div>

dann kann ein Token-Theme nur begrenzt helfen.

Besser ist eine semantische Umsetzung:

<div className="bg-primary text-primary-foreground">
  ...
</div>

Erst wenn Komponenten semantische Tokens verwenden, entfaltet ein Theme-Editor seine größte Wirkung.


Best Practices bei der Arbeit mit tweakcn

1. Mit einer klaren Designabsicht starten

Nicht einfach Farben verschieben, bis die Vorschau „irgendwie modern“ aussieht. Vorab sollten grundlegende Fragen beantwortet werden:

Diese Entscheidungen verhindern ein zufälliges Theme.


2. Nur wenige starke Farben verwenden

Viele Produkte funktionieren gut mit:

Zu viele konkurrierende Farben erschweren die visuelle Hierarchie.


3. Tokens semantisch einsetzen

Wenn möglich, sollten Komponenten mit semantischen Rollen arbeiten:

<Card className="border-border bg-card text-card-foreground">
  ...
</Card>

Statt:

<Card className="border-slate-200 bg-white text-slate-950">
  ...
</Card>

So bleiben spätere Theme-Änderungen kontrollierbar.


4. Den Dark Mode nicht nachträglich behandeln

Ein häufiges Problem ist, zunächst einen sehr detaillierten Light Mode zu entwickeln und den Dark Mode erst kurz vor Veröffentlichung anzusehen.

Besser ist es, beide Themes parallel zu pflegen. Besonders bei neuen Komponenten sollte früh geprüft werden:


5. Die Vorschau mit Produktrealität ergänzen

Ein Theme sollte mit echten Daten geprüft werden:

Die beste Theme-Vorschau ist letztlich die echte Anwendung.


6. Änderungen versionieren

Theme-Änderungen gehören in die Versionsverwaltung. Die CSS-Tokens sollten wie regulärer Produktionscode behandelt werden.

Sinnvoll sind:

Eine Änderung von --muted-foreground kann beispielsweise viele Hilfetexte, Tabellenbeschriftungen und deaktivierte Zustände beeinflussen.


Beispiel: Eine Marke in ein shadcn/ui-Theme übersetzen

Angenommen, ein B2B-Produkt besitzt diese gewünschte Wirkung:

Dann könnte ein Team folgende Entscheidungen treffen:

Gestaltungsbereich Mögliche Entscheidung
Primärfarbe Tiefes Indigo oder Blau
Neutrale Flächen Kühles Grau statt warmes Beige
Cards Weiße Fläche mit subtiler Border
Radius Mittel, beispielsweise 8px bis 10px
Schatten Dezent, hauptsächlich für Overlays
Typografie Gut lesbare Sans Serif
Destructive Klar erkennbares, aber nicht neonrotes Rot
Dark Mode Dunkles Blau-Grau statt reines Schwarz

Das Theme könnte anschließend in tweakcn visuell abgestimmt werden. Danach werden die resultierenden Tokens exportiert und in globals.css übernommen.

Die eigentliche Produktarbeit bleibt dennoch bestehen: Tabellen, Formulare, Content-Hierarchie und Interaktionslogik müssen weiterhin gezielt gestaltet werden.


Vergleich: manuelle Konfiguration vs. tweakcn

Aspekt Manuelle CSS-Konfiguration tweakcn
Bearbeitung Direkt in CSS-Dateien Visuell im Browser
Vorschau Über die lokale Anwendung Sofortige Komponenten-Vorschau
Einstieg CSS- und Token-Verständnis nötig Niedrigere Einstiegshürde
Kontrolle Vollständig und direkt Vollständig nach Code-Export
Geschwindigkeit beim Experimentieren Eher langsamer Meist deutlich schneller
Kontrastprüfung Manuell mit zusätzlichen Tools Visuell leichter erkennbar, aber weiterhin zu prüfen
Produktintegration Sofort im Projekt Export und Übernahme erforderlich
Langfristige Quelle der Wahrheit Repository Repository, nicht der Browser-Editor

Der letzte Punkt ist besonders wichtig:

Die endgültige Quelle der Wahrheit sollte immer der versionierte Theme-Code im Projekt sein — nicht ein nicht dokumentierter Browserzustand.

tweakcn ist ideal zum Gestalten, Erkunden und Generieren. Der exportierte Code gehört danach in die normale Entwicklungs- und Review-Praxis.


Fazit

tweakcn.com ist ein visueller Theme-Editor für das shadcn/ui-Ökosystem. Er unterstützt Teams dabei, Design Tokens wie Farben, Oberflächen, Typografie, Radius und Dark-Mode-Werte schneller und anschaulicher zu konfigurieren.

Der größte Nutzen entsteht, wenn ein Projekt bereits konsequent auf semantische shadcn/ui-Tokens setzt. Dann kann eine zentrale Theme-Änderung viele Komponenten gleichzeitig beeinflussen — von Buttons und Inputs bis zu Dialogen, Cards und Tabellen.

tweakcn ist besonders hilfreich für:

Es ersetzt jedoch weder UX-Design noch Barrierefreiheitsprüfung oder eine saubere Komponentenarchitektur. Seine Stärke liegt in der schnellen, visuellen Übersetzung von Marken- und Stilentscheidungen in ein tokenbasiertes UI-Theme.

Syntax Highlighting

Syntax Highlighting

Syntax-Highlighter-Libraries für JS: Überblick & Empfehlung 🎨

Für die direkte CDN-Einbindung in eine Website (ohne Build-Tools wie Webpack/Vite) kommen praktisch drei relevante Kandidaten in Frage. Hier eine Einordnung – speziell mit Blick auf deinen Workflow mit Bricks Builder und Divi.

Die wichtigsten Libraries

Library Prinzip CDN-tauglich? Besonderheit
Prism.js Client-seitig, statische Grammatiken ✅ Sehr gut Riesiges Theme-Angebot, Plugin-System (Zeilennummern, Copy-Button, Diff-Highlighting)
highlight.js Client-seitig, automatische Spracherkennung ✅ Sehr gut Erkennt Sprache oft automatisch, wenn du class="language-xxx" vergisst
Shiki Nutzt die TextMate-Grammatiken von VS Code ⚠️ Bedingt Optisch das genaueste Highlighting, aber eigentlich für Build-Time/SSR gedacht – für reines CDN-Einbinden overkill
Rainbow.js Client-seitig, minimalistisch Kaum noch weiterentwickelt – eher meiden
Torchlight API-basiert (Server rendert HTML) ⚠️ Kostenpflichtig, kein "echtes" CDN-Snippet

🏆 Empfehlung: Prism.js

Für deinen Anwendungsfall – Code-Snippets per Custom-HTML-Element in Bricks oder Divi einbinden – ist Prism.js die beste Wahl:

Minimal-Setup per CDN

Mit dem Autoloader-Plugin musst du dir um einzelne Sprachdateien keine Gedanken machen – Prism lädt die passende Grammatik automatisch nach, sobald es ein <code class="language-php"> (oder -js, -css etc.) findet.

<pre><code class="language-php">
function hello() {
    echo "Hallo Welt";
}
</code></pre>

In Bricks Builder einbinden

Alternative: highlight.js

Wenn dir automatische Spracherkennung ohne language-xxx-Klasse wichtiger ist als Theme-Vielfalt, ist highlight.js die zweitbeste Option:

Nachteil gegenüber Prism: etwas größere Grundlast und weniger flexibles Plugin-Ökosystem (z. B. für Zeilennummern braucht man Zusatz-Snippets).

Warum nicht Shiki?

Shiki liefert zwar das optisch genaueste Ergebnis (gleiche Engine wie VS Code), ist aber primär für Server-Side-Rendering oder Build-Time-Highlighting konzipiert. Für ein einfaches CDN-Skript auf einer WordPress-Seite ist der Overhead (WASM-basierte Oniguruma-Engine, größere Bundle-Größe) unnötig komplex – das würde dir ohne tiefere JS-Kenntnisse mehr Kopfschmerzen als Nutzen bringen.


Kurz gesagt: 🎯 Prism.js ist für dein Setup der Sweet Spot aus einfacher CDN-Einbindung, geringer Ladezeit und hoher visueller Anpassbarkeit über CSS – ganz ohne dass du dich mit WordPress-internen PHP-Prozessen auseinandersetzen musst.