Skip to content

Türchen 11 – CSS Custom Properties

Published: at 07:00 AM

CSS Custom Properties, oft einfach als „CSS-Variablen“ bezeichnet, gehören zu den wichtigsten Werkzeugen moderner CSS-Architektur. Anders als Präprozessor-Variablen (Sass, Less) werden sie zur Runtime ausgewertet, können vererbt, überschrieben und kontextabhängig verändert werden.

Genau das macht sie so mächtig. Sie sind nicht nur ein Hilfsmittel für Farben, sondern ein zentraler Baustein für Designsysteme, themingfähige Interfaces, fluides Design und komponentenbasierte Architekturen.

Dieses Türchen erklärt, wie Custom Properties funktionieren, welche Fallstricke existieren und wie man sie professionell einsetzt.

Der Unterschied zu Sass-Variablen

Viele Entwickler nutzen Sass seit Jahren und glauben zunächst, CSS Custom Properties seien einfach „das Gleiche in reinem CSS”, nur ohne Präprozessor. Diese Annahme ist fundamental falsch und führt oft dazu, dass das volle Potenzial von Custom Properties nicht erkannt wird.

Der entscheidende Unterschied liegt im Zeitpunkt der Auflösung. Sass-Variablen existieren nur während des Build-Prozesses, während CSS Custom Properties zur Laufzeit im Browser leben und arbeiten. Das klingt zunächst nach einem technischen Detail, hat aber weitreichende Konsequenzen für die gesamte Architektur.

Sass-Variablen – statisch zur Build-Zeit

Betrachten wir ein klassisches Sass-Beispiel:

$primary: #0d6efd;

.button {
  background: $primary;
}

Wenn Sass diesen Code kompiliert, wird die Variable $primary durch ihren Wert ersetzt. Das resultierende CSS enthält keine Variable mehr, sondern den direkten Wert. Der Browser sieht später nur noch background: #0d6efd. Die Variable selbst existiert im Endprodukt nicht mehr.

Diese Build-Time-Auflösung bedeutet, dass Sass-Variablen vollkommen statisch sind. Sobald das CSS kompiliert ist, ist alles in Stein gemeißelt. Man kann die Farbe nicht mehr ändern, ohne den gesamten Build-Prozess erneut durchzuführen. Komponenten können die Variable nicht überschreiben, weil es zur Laufzeit keine Variable gibt, die man überschreiben könnte. User-Settings wie Dark Mode können nicht darauf reagieren, weil der Wert bereits fest verdrahtet ist. JavaScript kann die Variable nicht anpassen, weil sie im Browser gar nicht existiert.

Diese Einschränkungen sind nicht trivial. Sie bedeuten, dass man für jede Variante eines Themes, für jeden unterschiedlichen Zustand, für jeden Kontext separate CSS-Regeln schreiben und kompilieren muss. Die gesamte Variabilität muss zur Build-Zeit antizipiert und vorbereitet werden.

CSS Custom Properties – dynamisch zur Laufzeit

CSS Custom Properties funktionieren fundamental anders:

:root {
  --primary: #0d6efd;
}

.button {
  background: var(--primary);
}

Dieser Code wird nicht aufgelöst oder ersetzt. Die Variable --primary und der var()-Aufruf bleiben im finalen CSS erhalten und werden vom Browser zur Laufzeit ausgewertet. Wenn der Browser das Element stylt, schaut er nach, welchen Wert --primary zu diesem Zeitpunkt in diesem Kontext hat, und verwendet diesen.

Diese Runtime-Natur eröffnet völlig neue Möglichkeiten. Die Variable kann an jeder Stelle im DOM-Baum überschrieben werden. Eine Komponente kann die Variable in ihrem eigenen Scope neu definieren, und alle Kinder übernehmen automatisch den neuen Wert. Das passiert alles im Browser, ohne jeden Build-Schritt.

CSS Custom Properties sind vererbar wie normale CSS-Properties. Sie folgen der Kaskade. Sie können von JavaScript dynamisch verändert werden, indem man einfach element.style.setProperty('--primary', '#ff0000') aufruft. Sie reagieren sofort auf Kontextänderungen wie das Hinzufügen einer CSS-Klasse oder eines Data-Attributs.

Ein User kann zur Laufzeit zwischen Light und Dark Mode wechseln, und alle Variablen passen sich sofort an, ohne dass neues CSS geladen werden muss. Eine Komponente kann in verschiedenen Kontexten unterschiedliche Werte für dieselbe Variable nutzen. Ein Dashboard kann seine Farbschema dynamisch anpassen, basierend auf User-Präferenzen, die aus einer Datenbank geladen werden.

Diese Runtime-Natur ist der entscheidende Vorteil und der Grund, warum CSS Custom Properties nicht einfach „Sass-Variablen in nativem CSS” sind, sondern ein komplett neues, mächtigeres Paradigma darstellen.

Vererbung – das mächtigste Merkmal

CSS Custom Properties verhalten sich wie normale CSS-Eigenschaften. Sie vererben sich entlang des DOM-Baums.

Beispiel:

:root {
  --text-color: #333;
}

.article {
  color: var(--text-color);
}

Wird im Artikel-Kontext etwas überschrieben:

.article.highlight {
  --text-color: #0055aa;
}

Alle Kinder übernehmen automatisch die neue Farbe ohne jede weitere Regel. Das ist keine Magic, sondern einfach normale CSS-Vererbung, wie wir sie von Properties wie color oder font-family kennen.

Diese Vererbungsmechanik macht Custom Properties zum idealen Werkzeug für zahlreiche Architektur-Pattern. Farbsysteme profitieren enorm davon, weil man einen Color-Token an der Wurzel definieren und in allen Komponenten verwenden kann, während einzelne Komponenten oder Bereiche die Farbe für ihren Scope überschreiben können. Eine Highlight-Section kann alle Primärfarben anders definieren, ohne jede Komponente darin einzeln anzupassen.

Spacing-Systeme werden durch Custom Properties konsistent und gleichzeitig flexibel. Man definiert ein Set von Spacing-Token wie --space-xs, --space-s, --space-m zentral, und jede Komponente verwendet diese. Wenn eine bestimmte Section kompakter sein soll, überschreibt sie einfach die Spacing-Variablen, und alle verschachtelten Komponenten passen sich automatisch an.

Typografische Skalen funktionieren nach demselben Prinzip. Die Basis-Schriftgrößen werden im Root definiert, aber eine Sidebar kann die Skala herunterskalieren, während ein Hero-Bereich sie hochskaliert. Alle Überschriften, Texte und UI-Elemente in diesen Bereichen übernehmen automatisch die angepassten Größen.

Komponenten-Varianten werden dadurch deutlich einfacher. Statt separate CSS-Klassen für button--primary, button--secondary, button--danger zu schreiben, die jeweils alle Properties neu definieren, kann man eine einzige .button-Komponente haben, die Variablen verwendet. Die Varianten überschreiben dann nur die Variablen, nicht die gesamte Styling-Logik.

Theming wird mit dieser Vererbung geradezu trivial. Man definiert alle Theme-Tokens im Root oder einem [data-theme]-Selektor, und die gesamte Applikation passt sich automatisch an. Keine Cascading-Konflikte, keine Spezifitäts-Kämpfe, keine dutzenden Override-Regeln. Nur Variablen, die vererbt und überschrieben werden.

Praktisches Beispiel: Theming einer Komponente

Basiswerte:

:root {
  --card-bg: #ffffff;
  --card-border: #e5e5e5;
}

Komponentenspezifisch überschreiben:

.card--dark {
  --card-bg: #1c1c1c;
  --card-border: #333333;
}

Verwendung:

.card {
  background: var(--card-bg);
  border: 1px solid var(--card-border);
}

Die Card reagiert auf ihren eigenen Kontext, ohne zusätzliche Klassen am Container.

Fallbacks – essenziell für robustes CSS

Eine der oft übersehenen, aber kritischen Features von Custom Properties sind Fallbackwerte. Die var()-Funktion kann einen zweiten Parameter enthalten, der als Fallback dient:

color: var(--headline-color, #111);

Dieser Fallback wird verwendet, wenn die Variable --headline-color nicht definiert ist oder einen ungültigen Wert enthält. Das klingt zunächst nach einem Nice-to-have-Feature, ist aber essenziell für robustes, produktionsreifes CSS.

Das Problem ist subtil aber real. CSS-Variablen können leer sein oder ungültige Werte enthalten, ohne dass ein offensichtlicher Fehler auftritt. Anders als bei statischen Werten, wo ein Tippfehler wie color: blu einfach ignoriert wird und der Browser den vorherigen Wert beibehält, führt eine leere oder ungültige Variable dazu, dass die Property komplett ausfällt.

Ein Praxisfall, den ich in Code-Reviews immer wieder sehe:

:root {
  --spacing-l: ;
}

Diese Variable wurde definiert, aber der Wert fehlt oder wurde versehentlich gelöscht. Jede Komponente, die diese Variable nutzt, verliert ihre Spacing-Definition komplett:

.section {
  margin-top: var(--spacing-l);
}

Die Section hätte hier plötzlich gar kein margin-top mehr. Nicht einen Wert von 0, nicht einen Default-Wert, sondern gar keinen Wert. Die Property fällt komplett weg, als hätte man sie nie definiert. Das kann zu subtilen Layout-Brüchen führen, die schwer zu debuggen sind.

Mit einem Fallback ist das Problem gelöst:

margin-top: var(--spacing-l, 2rem);

Jetzt hat die Section immer ein vernünftiges Margin, selbst wenn die Variable aus irgendeinem Grund nicht verfügbar ist. Der Fallback ist eine Art Sicherheitsnetz.

Fallbacks sind besonders wichtig in Komponentenbibliotheken und Designsystemen, die in verschiedenen Projekten eingesetzt werden. Man kann nie garantieren, dass alle notwendigen Variablen immer korrekt definiert sind. Mit Fallbacks sind Komponenten defensiv programmiert und funktionieren auch in suboptimalen Umgebungen.

Ein weiterer Use Case für Fallbacks sind verschachtelte Variablen-Systeme. Man kann eine Variable als Fallback für eine andere Variable verwenden:

background: var(--component-bg, var(--section-bg, var(--page-bg, #ffffff)));

Das System prüft erst, ob die Komponente eine eigene Background-Farbe definiert. Falls nicht, verwendet es die Section-Background. Falls auch die nicht definiert ist, die Page-Background. Und falls gar nichts definiert ist, fällt es auf Weiß zurück. Diese Kaskade von Fallbacks ermöglicht sehr flexible und robuste Theming-Systeme.

Fallbacks gehören zu jedem produktionsreifen Einsatz von Custom Properties. Sie sind nicht optional, sondern ein essentieller Teil defensiver CSS-Programmierung.

Custom Properties für Layouts nutzen

Der häufigste Einsatz von Custom Properties sind Farben, aber ihr wahres Potenzial zeigt sich, wenn man sie für Layoutwerte nutzt. Hier entfalten sie eine Flexibilität, die mit statischen Werten schlichtweg nicht erreichbar ist.

Betrachten wir ein klassisches Layout-Problem. Eine Sidebar, deren Breite dynamisch gesteuert werden muss. Mit traditionellem CSS müsste man entweder mehrere CSS-Klassen für verschiedene Breiten vorbereiten oder mit JavaScript direkt inline-Styles manipulieren. Beides ist suboptimal.

Mit Custom Properties wird das elegant:

:root {
  --sidebar-width: 280px;
}

.sidebar {
  width: var(--sidebar-width);
}

Die Sidebar hat eine Default-Breite von 280 Pixeln. Das ist der Standard für die gesamte Anwendung. Aber jetzt kommt die Magic. Diese Breite kann von außen verändert werden, ohne die Komponente selbst anzufassen.

Ein User könnte eine Einstellung in der App haben: “Breite Sidebar” oder “Schmale Sidebar”. Mit JavaScript passt man einfach die Variable an:

document.documentElement.style.setProperty('--sidebar-width', '350px');

Die gesamte Anwendung reagiert sofort. Nicht nur die Sidebar selbst, sondern auch alle Layouts, die sich auf die Sidebar-Breite beziehen, passen sich automatisch an. Ein Grid-Layout, das mit grid-template-columns: var(--sidebar-width) 1fr arbeitet, adaptiert sich sofort. Ein Media Query, das auf die Sidebar-Breite Bezug nimmt, funktioniert dynamisch.

Das ist ein komplett anderes Architekturlevel als hardcodierte Layoutwerte. Die Layout-Logik ist von den konkreten Werten entkoppelt. Man kann verschiedene Layout-Modi haben, ohne verschiedene CSS-Dateien oder komplexe Override-Logik. Die Anwendung wird konfigurierbar, ohne dass man dutzende Varianten vorbereiten muss.

Ein weiteres Beispiel: Column-Breiten in einem Dashboard. Verschiedene User bevorzugen verschiedene Layouts. Manche wollen breite Spalten für Content, andere schmale Spalten für mehr Übersicht. Mit Custom Properties können User ihre bevorzugten Breiten einstellen, diese werden in localStorage oder einer Datenbank gespeichert, und beim nächsten Besuch wird das Layout exakt so wiederhergestellt.

Das funktioniert nicht nur für Breiten, sondern für alle numerischen Layoutwerte. Abstände, Höhen, Border-Radien, Shadow-Stärken, Transition-Dauern und das alles kann als Custom Property definiert und dynamisch angepasst werden. Das macht Interfaces nicht nur flexibler, sondern auch zugänglicher, weil User die Darstellung an ihre Bedürfnisse anpassen können.

Custom Properties + Responsive Design

Die Kombination von Custom Properties mit modernen Responsive-Techniken wie clamp(), die wir an Tag 10 kennengelernt haben, eröffnet faszinierende Möglichkeiten. Man kann fluide, responsive Werte zentral definieren und überall konsistent nutzen.

Ein elegantes Pattern kombiniert Viewport-Einheiten und clamp() in Custom Properties:

:root {
  --space-m: clamp(1rem, 1vw + 0.5rem, 2rem);
}

.section {
  padding: var(--space-m);
}

Diese Definition ist mächtig in ihrer Einfachheit. Der mittlere Abstand --space-m ist nicht statisch, sondern wächst fluid mit dem Viewport. Bei schmalen Displays beträgt er 1rem, bei breiten bis zu 2rem. Die Formel 1vw + 0.5rem sorgt für kontinuierliches, proportionales Wachstum.

Der entscheidende Vorteil ist die Zentralisierung. Dieser eine Wert wird an dutzenden oder hunderten Stellen in der Anwendung verwendet. Jede Section, jede Card, jede Grid-Gap kann var(--space-m) verwenden. Wenn man später entscheidet, dass die Skala anders wachsen soll, ändert man nur die Definition in :root. Alle Verwendungen passen sich sofort an.

Das ist ein fundamentaler Unterschied zu traditionellem responsive CSS, wo man an jeder Verwendungsstelle Media Queries schreiben müsste. Statt hunderte von Media Queries zu pflegen, hat man eine zentrale, fluide Definition. Die Komplexität wird an einem Punkt konzentriert, und der Rest des Codes bleibt einfach und lesbar.

Man kann ganze Typografie-Scales so definieren:

:root {
  --font-size-base: clamp(1rem, 0.5vw + 0.85rem, 1.125rem);
  --font-size-lg: clamp(1.25rem, 1vw + 1rem, 1.5rem);
  --font-size-xl: clamp(1.5rem, 1.5vw + 1rem, 2rem);
  --font-size-2xl: clamp(2rem, 2vw + 1.5rem, 3rem);
}

Jede Komponente verwendet diese Variablen. Das gesamte typografische System skaliert harmonisch über alle Viewport-Größen, ohne eine einzige Media Query in den Komponenten selbst.

Das gleiche Prinzip funktioniert für Spacing-Scales, Icon-Größen, Border-Radien und alle anderen numerischen Werte. Man definiert ein Set von fluiden Basis-Token, und die gesamte Anwendung wird automatisch responsive. Wartbarkeit und Konsistenz steigen dramatisch.

Custom Properties + Container Queries

Die Kombination ist besonders kraftvoll:

.card {
  container-type: inline-size;
  --card-padding: 1rem;
  padding: var(--card-padding);
}

@container (min-width: 500px) {
  .card {
    --card-padding: 2rem;
  }
}

Die Komponente überschreibt nur eine Variable, nicht die gesamte CSS-Eigenschaft. Das ist ein subtiler, aber extrem wichtiger Unterschied zur traditionellen Override-Logik.

Bei klassischem CSS müsste man innerhalb der Container Query die komplette padding-Property neu schreiben. Wenn die Card später weitere Padding-bezogene Änderungen bekommt, müsste man jeden Override-Punkt anfassen. Mit Custom Properties ändert man nur die Variable. Die Verwendung von padding: var(--card-padding) bleibt unverändert, egal wie viele verschiedene Kontexte unterschiedliche Padding-Werte benötigen.

Das führt zu deutlich weniger Override-Regeln im gesamten Codebase. Statt dutzende Selektoren zu haben, die dieselbe Property mit verschiedenen Werten neu definieren, hat man einen Selektor für die Basis-Verwendung und mehrere Kontexte, die nur die Variable überschreiben. Das CSS bleibt schlank und die Override-Intention ist sofort erkennbar.

Die Architektur wird dadurch sauberer und besser wartbar. Die Verantwortlichkeiten sind klar getrennt. Die Komponente definiert, wie Properties verwendet werden. Die Kontexte definieren, welche Werte diese Properties haben sollen. Diese Separation of Concerns ist genau das, was große Codebases wartbar macht.

Die Komponentenverwaltung wird einfacher, weil man Komponenten in verschiedenen Kontexten einsetzen kann, ohne ihren eigentlichen Code anzufassen. Eine Card-Komponente funktioniert im Hauptinhalt genauso wie in einer Sidebar, in einem Modal oder in einem Grid. Jeder Kontext definiert nur die Variablen, die für ihn sinnvoll sind, und die Komponente passt sich automatisch an. Das ist echte Komponentenautonomie und Wiederverwendbarkeit.

Häufige Fehler im Umgang mit Custom Properties

Trotz ihrer Einfachheit werden Custom Properties oft nicht optimal eingesetzt. Die folgenden Fehler sehe ich immer wieder in Code-Reviews. Sie zu vermeiden macht den Unterschied zwischen einem funktionierenden und einem wirklich professionellen System.

Fehler 1: Variablen zu tief und zu lokal definieren

Ein häufiger Anfängerfehler ist, Variablen dort zu definieren, wo sie verwendet werden:

.component {
  --primary: #0070f3;
}

Das scheint auf den ersten Blick logisch. Die Variable ist nah an ihrer Verwendung, aber es schafft massive Probleme. Erstens ist die Variable nur innerhalb .component und seinen Kindern verfügbar. Andere Komponenten können sie nicht nutzen. Man müsste die Farbe duplizieren oder komplexe Selektoren schreiben.

Zweitens wird das System inkonsistent. Wenn zehn verschiedene Komponenten alle ihre eigene --primary definieren, kann man nicht mehr garantieren, dass überall dieselbe Farbe verwendet wird. Jemand könnte versehentlich einen leicht anderen Hex-Wert verwenden, und plötzlich hat man ein inkonsistentes Design.

Die bessere Lösung ist, globale Design-Token zentral zu definieren:

:root {
  --primary: #0070f3;
}

Jetzt ist die Farbe überall verfügbar. Alle Komponenten nutzen garantiert denselben Wert. Wenn man die Primärfarbe ändert, ändert sie sich überall gleichzeitig. Das ist echte Konsistenz.

Die Regel ist einfach: Je grundlegender und wiederverwendbarer ein Wert, desto höher sollte er definiert werden. Globale Design-Token gehören in :root. Komponentenspezifische Variablen können in der Komponente selbst definiert werden, aber sie sollten auf globale Token aufbauen, nicht komplett eigenständig sein.

Fehler 2: Variablen nur für Farben nutzen und ihr volles Potenzial ignorieren

Viele Entwickler sehen Custom Properties als “CSS-Farben-Variablen” und nutzen sie fast ausschließlich für Hex-Codes. Das ist einer der größten verpassten Chancen in modernem CSS. Die Limits vieler Designsysteme entstehen direkt aus dieser Einschränkung.

Custom Properties können jeden CSS-Wert enthalten. Nicht nur Farben, sondern absolut alles, was man in CSS schreiben kann. Und genau diese Universalität macht sie so mächtig.

Betrachten wir ein vollständigeres Token-System:

--button-radius: 6px;
--button-padding: 0.5rem 1rem;
--transition-fast: 150ms ease-out;
--shadow-soft: 0 2px 8px rgba(0,0,0,0.08);

Ein Button-Radius als Variable bedeutet, dass man das gesamte Interface mit einer Änderung von abgerundeten zu eckigen Buttons wechseln kann. Kein Find-and-Replace durch hunderte Dateien, keine Gefahr, Stellen zu übersehen. Nur eine Definition in :root ändern.

Padding als Variable ermöglicht konsistente Button-Größen. Man kann verschiedene Button-Größen haben (compact, default, large), die alle nur die Padding-Variable überschreiben. Die restliche Button-Logik bleibt identisch.

Transitions als Variable sind besonders wertvoll für Animationkonsistenz. Alle Buttons, Links, Dropdowns, Modals können dieselbe Transition-Timing nutzen. Das Interface fühlt sich dadurch kohärent an. Wenn man später entscheidet, dass Animationen schneller oder langsamer sein sollen, ändert man einen Wert.

Shadows als Variable ermöglichen Elevation-Systeme wie in Material Design. Man definiert verschiedene Shadow-Stärken (--shadow-1, --shadow-2, --shadow-3), und Komponenten verwenden die passende Elevation für ihren Zweck. Ein Hover-State kann die Shadow verstärken, indem er nur die Variable ändert.

CSS Custom Properties sind Design-Tokens im wahrsten Sinne des Wortes. Tokens sind semantische, wiederverwendbare Einheiten des Designs. Sie repräsentieren Entscheidungen, nicht Werte. --transition-fast sagt “diese Animation ist schnell”, nicht “150ms”. Wenn man später entscheidet, dass “schnell” 100ms bedeutet, ändert man nur die Definition.

Diese Token-Denkweise ist fundamental für professionelle Design-Systeme. Sie entkoppelt das “Was” vom “Wie”. Komponenten sagen “ich brauche einen weichen Schatten”, nicht “ich brauche 0 2px 8px rgba(0,0,0,0.08)”. Das macht Systeme wartbar, adaptierbar und skalierbar.

Fehler 3: Chaotische Benennung ohne konsistentes System

Ein weiterer häufiger Fehler ist, Variablen nach Gutdünken zu benennen, ohne Struktur oder Systematik:

--padding: 1rem;
--spacing: 1.5rem;
--gap: 2rem;

Dieses System ist willkürlich und wird schnell unüberschaubar. Was ist der Unterschied zwischen padding, spacing und gap? Warum haben sie unterschiedliche Werte? Wenn man einen neuen Abstand braucht, wie nennt man ihn? --margin? --distance? --space? Das System kollabiert, sobald man über ein paar Variablen hinausgeht.

Die Lösung ist eine konsistente Skala mit systematischer Benennung:

--size-1: 0.5rem;
--size-2: 1rem;
--size-3: 1.5rem;
--size-4: 2rem;

Jetzt ist sofort klar, was los ist. Es gibt eine Größen-Skala von klein nach groß. --size-1 ist der kleinste Wert, --size-4 der größte. Wenn man einen neuen Wert braucht, fügt man --size-5 hinzu. Keine Verwirrung, keine Überschneidungen, keine Inkonsistenzen.

Diese numerische Skala macht Beziehungen zwischen Werten explizit. --size-4 ist doppelt so groß wie --size-2. Das ist nicht nur mathematisch klar, sondern auch visuell vorhersehbar. Designer und Entwickler sprechen dieselbe Sprache.

Man kann verschiedene Skalen für verschiedene Zwecke haben:

/* Spacing Scale */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 1rem;
--space-4: 1.5rem;
--space-5: 2rem;

/* Font Size Scale */
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;

Jede Skala hat ihre eigene Prefix (space-, text-), sodass klar ist, wofür sie gedacht ist. Die Benennung ist konsistent und vorhersehbar. Man kann die Skala erweitern, ohne das System zu brechen.

Einheitliche Skalen sind nicht nur nice-to-have, sie sind essenziell für Wartbarkeit großer Codebases. Sie reduzieren kognitive Last, machen Onboarding neuer Entwickler einfacher und verhindern die schleichende Fragmentierung, die viele Designsysteme über Zeit zerreißt.

Ein weiteres Beispiel: Light/Dark Mode mit minimaler Komplexität

Light und Dark Mode sind heute Standard in modernen Anwendungen. Traditionell erforderte das entweder das Laden unterschiedlicher Stylesheets oder komplexe Override-Kaskaden. Mit Custom Properties wird es geradezu trivial.

Das gesamte Theme kann mit minimal Code implementiert werden:

:root {
  --bg: #ffffff;
  --text: #111111;
}

[data-theme="dark"] {
  --bg: #111111;
  --text: #ffffff;
}

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

Das ist keine Vereinfachung fürs Beispiel. Das ist produktionsreifer Code. Die gesamte Anwendung kann zwischen Light und Dark Mode wechseln, indem man einfach das data-theme-Attribut am HTML-Element ändert.

Die Eleganz liegt in der Vererbung. Die Variablen werden in :root für Light Mode definiert. Der [data-theme="dark"] Selektor überschreibt sie. Da Custom Properties vererbt werden, haben alle Elemente im gesamten DOM automatisch Zugriff auf die richtigen Werte. Man braucht nicht jedes Element einzeln zu stylen, nicht dutzende Klassen zu verwalten, nicht komplexe Selektoren zu schreiben.

Der Theme-Wechsel erfolgt zur Laufzeit mit einer einzigen JavaScript-Zeile:

document.documentElement.setAttribute('data-theme', 'dark');

Sofort passen sich alle Elemente an. Keine neuen Stylesheets müssen geladen werden, keine Reflows werden getriggert, keine visuellen Glitches treten auf. Das Interface wechselt nahtlos von Light zu Dark.

In der Realität wird das System natürlich komplexer. Man hat nicht nur zwei Farben, sondern ein ganzes Color-System:

:root {
  --color-bg-primary: #ffffff;
  --color-bg-secondary: #f5f5f5;
  --color-text-primary: #111111;
  --color-text-secondary: #666666;
  --color-border: #e5e5e5;
  --color-accent: #0070f3;
}

[data-theme="dark"] {
  --color-bg-primary: #111111;
  --color-bg-secondary: #1a1a1a;
  --color-text-primary: #ffffff;
  --color-text-secondary: #999999;
  --color-border: #333333;
  --color-accent: #3291ff;
}

Aber das Prinzip bleibt dasselbe. Man definiert alle Color-Tokens für Light Mode, überschreibt sie für Dark Mode, und die gesamte Anwendung adaptiert sich automatisch. Keine Komponente muss speziellen Dark-Mode-Code enthalten. Sie verwenden einfach die Variablen, und die Variablen haben je nach Theme unterschiedliche Werte.

Das macht Theming nicht nur einfacher zu implementieren, sondern auch deutlich einfacher zu erweitern. Wenn man später ein drittes Theme, wie vielleicht ein High-Contrast-Theme für Accessibility hinzufügen möchte, fügt man einfach einen weiteren Selektor hinzu. Die Komponenten bleiben vollkommen unverändert.

Fazit

CSS Custom Properties sind weit mehr als nur „Variablen in CSS”. Sie sind ein fundamentales Architektur-Werkzeug, das die Art und Weise verändert, wie wir moderne Interfaces bauen. Ihre Runtimenatur macht sie dynamisch. Im Gegensatz zu Präprozessorvariablen, die zur Build-Zeit verschwinden, leben Custom Properties im Browser und können zur Laufzeit verändert werden. Das ermöglicht Theming, Userpräferenzen, kontextabhängiges Styling und dynamische Anpassungen, die mit statischen Werten unmöglich wären.

Ihre Vererbung macht sie mächtig. Sie folgen der CSS-Kaskade wie normale Properties. Ein Wert, der im Root definiert wird, ist überall verfügbar. Ein Wert, der in einem Container überschrieben wird, gilt für alle Kinder. Diese Vererbung eliminiert Redundanz und macht Overrides elegant und vorhersehbar.

Ihre Universalität macht sie flexibel. Sie sind nicht auf Farben beschränkt, sondern können jeden CSS-Wert enthalten. Spacing, Transitions, Shadows, Font-Sizes, Border-Radien das alles kann tokenisiert und zentral verwaltet werden. Das macht Designsysteme konsistent und wartbar.

Die Kombination mit modernen CSS-Features wie clamp() aus Tag 10 und Container Queries aus Tag 9 macht sie unverzichtbar. Man kann fluide, responsive Werte zentral definieren. Man kann Komponenten bauen, die ihre eigenen Variablen je nach verfügbarem Raum überschreiben. Das sind Architektur-Patterns, die vor Custom Properties schlichtweg nicht möglich waren.

Custom Properties machen Designsysteme konsistent, weil alle Komponenten dieselben Token verwenden. Sie machen Komponenten unabhängig, weil jede Komponente ihre eigenen Variablen definieren und überschreiben kann. Sie machen Theming trivial, weil man nur Variablen überschreiben muss, nicht ganze Komponenten. Und sie machen Responsive Design eleganter, weil fluide Werte zentral definiert und überall genutzt werden können.

CSS Custom Properties sind eines der zentralen Werkzeuge für professionelles, modernes CSS. Sie sind nicht mehr optional, sondern essentiell für jede größere Anwendung. Wer sie meistert, schreibt besseres, wartbareres, flexibleres CSS.


☕ Buy me a coffee

Wenn Dir meine Beiträge gefallen und sie Dir bei Deiner Arbeit helfen, würde ich mich über einen "Kaffee" und ein paar nette Worte von Dir freuen.

Buy me a coffee

Previous Post
Türchen 12 – State Styling ohne JavaScript
Next Post
Türchen 10 – Modern Responsive Design

Kommentare