CSS · Design System · Theming · Dark Mode
CSS Custom Properties für Theming
Design-Systeme mit --var und @property

Hardcodierte Farbwerte, die an zwanzig Stellen geändert werden müssen — das ist das Symptom eines fehlenden Theming-Systems. CSS Custom Properties schaffen die Grundlage für wartbare Design-Systeme: ein Ort für jeden Wert, kaskadierendes Überschreiben auf Komponentenebene und Dark Mode ohne eine Zeile JavaScript.

13 Min. Lesezeit --var · :root · @property · prefers-color-scheme · Design Tokens CSS3 · Chrome 85+ · Firefox 89+ · Safari 15.4+

1. Was CSS Custom Properties vom Präprozessor-Variablen unterscheidet

CSS Custom Properties — oft als CSS-Variablen bezeichnet — lösen ein anderes Problem als Sass- oder Less-Variablen. Sass-Variablen sind Compile-Time-Konstanten: Sie werden beim Build-Prozess durch ihre Werte ersetzt. Das finale CSS enthält keine Variablen mehr, sondern nur noch Werte. CSS Custom Properties hingegen sind echte Laufzeitvariablen — sie existieren im Browser, können zur Laufzeit per JavaScript geändert werden, reagieren auf Media Queries und CSS-Selektoren und kaskadieren durch den DOM-Baum genau wie normale CSS-Eigenschaften.

Dieser Unterschied ist fundamental für Theming-Systeme. Mit Sass-Variablen ist ein Theme-Wechsel ohne Seitenneuladung nicht möglich — der Wert ist bereits kompiliert. Mit CSS Custom Properties genügt eine einzige JavaScript-Zeile (document.documentElement.style.setProperty('--color-primary', '#7c3aed')) oder ein CSS-Selektor-Wechsel, um das gesamte Theme zu ändern. Dark Mode lässt sich vollständig in CSS abbilden, ohne dass JavaScript eine Klasse umschalten muss. Das macht CSS Custom Properties zur Grundlage jedes modernen, wartbaren Design-Systems.


/* CSS Custom Properties: the complete theming foundation */

/* 1. Design Token Layer — single source of truth */
:root {
  /* Color Palette — raw values, not semantic */
  --violet-50:  #f5f3ff;
  --violet-100: #ede9fe;
  --violet-200: #ddd6fe;
  --violet-500: #8b5cf6;
  --violet-700: #6d28d9;
  --violet-900: #4c1d95;

  /* Semantic Layer — purpose over value */
  --color-primary:    var(--violet-700);
  --color-primary-fg: #ffffff;
  --color-surface:    #ffffff;
  --color-surface-raised: #f8fafc;
  --color-text:       #0f172a;
  --color-text-muted: #64748b;
  --color-border:     #e2e8f0;

  /* Spacing Scale */
  --space-1:  0.25rem;
  --space-2:  0.5rem;
  --space-4:  1rem;
  --space-8:  2rem;
  --space-16: 4rem;

  /* Typography */
  --font-size-sm:   0.875rem;
  --font-size-base: 1rem;
  --font-size-lg:   1.125rem;
  --font-size-xl:   1.25rem;
  --font-size-4xl:  2.25rem;
  --font-weight-normal: 400;
  --font-weight-bold:   700;

  /* Borders and Radius */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-xl: 1rem;
  --radius-full: 9999px;
}

2. Syntax und Scoping: --var und :root

CSS Custom Properties werden mit zwei Bindestrichen als Präfix deklariert: --meine-farbe: #7c3aed. Der Name ist case-sensitive — --Color und --color sind zwei verschiedene Eigenschaften. Verwendet werden sie mit der var()-Funktion: color: var(--meine-farbe). Das Scoping folgt dem CSS-Kaskadenmodell: Eine CSS Custom Property, die auf :root definiert ist, ist im gesamten Dokument verfügbar. Eine Eigenschaft, die auf einem spezifischen Element definiert ist, gilt nur für dieses Element und seine Nachfahren.

Das Kaskadierungsverhalten ist der entscheidende Vorteil gegenüber Sass-Variablen für Theming. Eine auf :root definierte CSS Custom Property wie --button-bg: var(--color-primary) kann für eine spezifische Komponente überschrieben werden, indem dieselbe Property auf dem Komponenten-Element einen neuen Wert bekommt: .card { --button-bg: var(--color-surface) }. Alle Schaltflächen innerhalb dieser Karte verwenden automatisch die überschriebene Variable — ohne eine einzige spezifischere Selektor-Regel.

3. Fallback-Werte und defensive Programmierung

Die var()-Funktion akzeptiert einen optionalen Fallback-Wert als zweites Argument: color: var(--text-color, #0f172a). Wenn --text-color nicht definiert oder ungültig ist, wird der Fallback-Wert verwendet. Fallbacks können geschachtelt werden: var(--text-color, var(--color-text, #0f172a)) — dieser Ausdruck versucht zuerst --text-color, dann --color-text, dann den Literal-Wert. Das ermöglicht defensive CSS Custom Properties-Implementierungen, die auch funktionieren, wenn übergeordnete Theming-Schichten fehlen.

Ein wichtiges Detail: Der Fallback-Wert wird erst evaluiert, wenn die Variable nicht aufgelöst werden kann. Das bedeutet, er wird auch bei einer ungültig gesetzten Variable verwendet. Wenn --button-bg auf none gesetzt ist — ein für background-color ungültiger Wert — greift der Fallback. Mit @property kann dieses Verhalten gesteuert werden: Eine typisierte CSS Custom Property ignoriert Werte, die nicht dem deklarierten Typ entsprechen, und fällt auf den initial-value zurück. Das macht Theming-Systeme mit @property robuster gegenüber falschen Werten.

4. Dark Mode ohne JavaScript: prefers-color-scheme

Dark Mode mit CSS Custom Properties erfordert kein JavaScript. Die Media Query @media (prefers-color-scheme: dark) erkennt die Systemeinstellung des Nutzers und überschreibt die semantischen Farbvariablen auf :root. Das Ergebnis ist ein vollständiger Theme-Wechsel ohne DOM-Manipulation: Alle Komponenten, die auf die semantischen CSS Custom Properties wie --color-surface oder --color-text referenzieren, wechseln automatisch ihre Farben, sobald der Nutzer die Systemeinstellung ändert. Das System reagiert sofort, ohne Seitenneuladung und ohne JavaScript.

Für einen manuellen Theme-Toggle, bei dem der Nutzer per Knopfdruck zwischen Hell und Dunkel wechseln kann, genügt eine CSS-Klasse auf <html> oder <body>: html.dark { --color-surface: #0f172a; --color-text: #f1f5f9; }. JavaScript schaltet nur die Klasse um — die gesamte Theming-Logik bleibt in CSS. Dieses Pattern ist deutlich wartbarer als JavaScript-basiertes Theming, bei dem Farben programmatisch gesetzt werden. Die CSS Custom Properties bilden eine klare Grenze zwischen Logik (Klassenumschalten) und Präsentation (Farbwerte).


/* Dark Mode via prefers-color-scheme — no JavaScript required */

:root {
  /* Light mode defaults */
  --color-surface:        #ffffff;
  --color-surface-raised: #f8fafc;
  --color-text:           #0f172a;
  --color-text-muted:     #64748b;
  --color-border:         #e2e8f0;
  --color-primary:        #7c3aed;
  --color-primary-hover:  #6d28d9;
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
}

/* System dark mode — override semantic tokens only */
@media (prefers-color-scheme: dark) {
  :root {
    --color-surface:        #0f172a;
    --color-surface-raised: #1e293b;
    --color-text:           #f1f5f9;
    --color-text-muted:     #94a3b8;
    --color-border:         #334155;
    --color-primary:        #a78bfa;
    --color-primary-hover:  #c4b5fd;
    --shadow-sm: 0 1px 3px rgba(0,0,0,0.4);
  }
}

/* Manual toggle — class on <html> overrides media query */
html.theme-dark {
  --color-surface:        #0f172a;
  --color-surface-raised: #1e293b;
  --color-text:           #f1f5f9;
  --color-text-muted:     #94a3b8;
  --color-border:         #334155;
  --color-primary:        #a78bfa;
}

/* Components use only semantic tokens — automatically themed */
.card {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  border-radius: var(--radius-xl);
  padding: var(--space-4);
  box-shadow: var(--shadow-sm);
}

.btn-primary {
  background: var(--color-primary);
  color: var(--color-primary-fg, #fff);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
}

5. Component-Theming: Variablen auf Element-Ebene

CSS Custom Properties auf Komponentenebene ermöglichen eine Theming-Architektur, bei der jede Komponente ihre eigene Schnittstelle aus lokalen Variablen definiert. Statt einer langen Liste globaler Farben definiert eine Button-Komponente lokale CSS Custom Properties wie --btn-bg, --btn-fg und --btn-radius, die standardmäßig auf globale Tokens verweisen: --btn-bg: var(--color-primary). Der Verwender der Komponente kann diese lokalen Variablen überschreiben, ohne auf interne Selektoren angewiesen zu sein: .my-context { --btn-bg: var(--color-secondary) }.

Dieses Pattern — oft als CSS-API einer Komponente bezeichnet — schafft eine klare Schnittstelle zwischen dem Implementierungsdetail (interne Selektoren und Eigenschaften) und der öffentlichen Theming-API (dokumentierte Custom Properties). Web-Components-Bibliotheken wie Shoelace und Open UI verwenden dieses Pattern systematisch: Jede Komponente dokumentiert ihre Custom-Property-API, und Verwender können das Aussehen vollständig anpassen, ohne in Implementierungsdetails einzugreifen. Das ist der Kern skalierbarer Komponentenarchitektur mit CSS Custom Properties.

6. @property: typisierte CSS Custom Properties

@property ist eine CSS-At-Regel, die eine CSS Custom Property mit einem Typ, einem Initialwert und einer Vererbungsregel registriert. Ohne @property sind CSS Custom Properties typlos — der Browser behandelt ihren Wert als beliebigen String und interpoliert ihn nicht. Mit @property kann eine Property als syntax: "<color>", syntax: "<length>", syntax: "<number>" oder mit komplexen Syntax-Strings wie "<length> | <percentage>" typisiert werden.

Die Typisierung hat drei Konsequenzen. Erstens: Ungültige Werte fallen auf den initial-value zurück, statt propagiert zu werden. Zweitens: Typisierte CSS Custom Properties können in calc()-Ausdrücken verwendet werden, ohne dass explizite Unit-Multiplikation nötig ist — calc(var(--spacing) * 2) funktioniert korrekt, wenn --spacing als <length> typisiert ist. Drittens, und das ist der wichtigste Punkt: Typisierte CSS Custom Properties können animiert werden. Der Browser kann zwischen zwei Werten interpolieren, weil er den Typ kennt. Ein untypisierer String kann nicht interpoliert werden; eine typisierte Farbe oder Länge kann es.


/* @property: typed CSS Custom Properties */

/* Typed color — enables transition/animation */
@property --color-accent {
  syntax: "<color>";
  inherits: true;
  initial-value: #7c3aed;
}

/* Typed number — enables animation and calc() */
@property --progress {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

/* Typed length — safe in calc() expressions */
@property --card-padding {
  syntax: "<length>";
  inherits: false;
  initial-value: 1rem;
}

/* Use in a component */
.progress-bar {
  --progress: 0;           /* initial: 0% */
  width: calc(var(--progress) * 1%);
  background: var(--color-accent);
  transition: --progress 0.4s ease, --color-accent 0.3s ease;
  height: 4px;
  border-radius: var(--radius-full, 9999px);
}

/* Animate to 75% — browser interpolates because type is <number> */
.progress-bar.loaded {
  --progress: 75;
}

/* Color transitions work only with @property <color> type */
.theme-switcher:hover {
  --color-accent: #4a1d96;  /* smoothly transitions from #7c3aed */
}

7. @property für animierbare Eigenschaften

Die Animierbarkeit typisierter CSS Custom Properties mit @property eröffnet eine neue Kategorie von CSS-Animationseffekten. Ohne @property kann man eine CSS Custom Property in transition oder @keyframes erwähnen, aber der Browser kann den Wert nicht interpolieren — er springt sofort von Anfang zu Ende. Mit @property und dem richtigen Typ kann der Browser zwischen zwei Farbwerten, zwei Längenwerten oder zwei Zahlenwerten interpolieren, als wäre es eine normale CSS-Eigenschaft.

Ein besonders elegantes Anwendungsmuster: Gradient-Animationen. Ohne typisierte CSS Custom Properties kann ein CSS-Gradient nicht animiert werden — Gradients sind keine interpolierbare CSS-Eigenschaft. Mit @property können einzelne Farbwerte innerhalb des Gradients als typisierte Properties definiert werden, die dann separat animiert werden. Das Ergebnis ist ein fließend animierter Gradient — ein Effekt, der ohne @property nur mit JavaScript oder Canvas-Rendering möglich wäre. Die Implementierung besteht aus zwei @property-Deklarationen, zwei CSS-Variablen-Referenzen im background-Attribut und einer @keyframes-Animation auf den Variablen.

8. Design Tokens mit CSS Custom Properties

Design Tokens sind die atomaren Einheiten eines Design-Systems — Farben, Abstände, Schriftgrößen und andere Basis-Werte, die systemweit konsistent sein sollen. CSS Custom Properties sind das natürliche Implementierungsformat für Design Tokens im Browser. Die übliche Architektur ist dreischichtig: Die unterste Schicht enthält rohe Werte (--violet-700: #6d28d9), die mittlere Schicht enthält semantische Aliases (--color-primary: var(--violet-700)) und die oberste Schicht enthält komponentenspezifische Tokens (--btn-bg: var(--color-primary)).

Dieses Token-System lässt sich direkt aus Design-Tools wie Figma exportieren. Tools wie Style Dictionary oder Tokens Studio konvertieren Figma-Variablen in CSS Custom Properties. Das schafft eine durchgängige Kette vom Design-Entwurf bis zur CSS-Implementierung ohne manuellen Übertrag. Wenn der Designer die Primärfarbe in Figma ändert, wird die Änderung automatisch in die CSS-Token-Datei übersetzt und landet per CI/CD im Stylesheet. Der Browser sieht dieselbe CSS Custom Property-Struktur — nur die Werte ändern sich, nie die Property-Namen.

9. Custom Properties vs. Sass-Variablen im Vergleich

Beide Systeme haben ihre Stärken. CSS Custom Properties sind laufzeitdynamisch, kaskadieren und funktionieren ohne Build-Schritt. Sass-Variablen sind statisch, werden kompiliert und haben eine ausgereifte Toolchain. Für Theming-Systeme, die auf Laufzeitanpassung angewiesen sind, sind CSS Custom Properties unverzichtbar. Für statische Design-Entscheidungen können beide koexistieren.

Merkmal Sass-Variablen CSS Custom Properties Gewinner
Laufzeit-Änderung Nicht möglich — compile-time Ja — setProperty() per JS CSS Custom Properties
Dark Mode Nur mit separatem CSS-Build Native via @media dark CSS Custom Properties
Animierbarkeit Nicht animierbar Mit @property typisierbar CSS Custom Properties
Browser-Kompatibilität Kompiliertes CSS — IE11-fähig IE11 nicht unterstützt Sass (Legacy-Support)
Kaskaden-Scoping Kein — statischer Wert Ja — kaskadiert durch DOM CSS Custom Properties

Die beste Strategie für moderne Projekte kombiniert beide Systeme: Sass-Variablen für Build-Time-Konfiguration (Breakpoints, Grid-Definitionen), CSS Custom Properties für alle Theming-Werte, die sich laufzeitdynamisch verhalten sollen (Farben, Abstände, Dark Mode). Tailwind CSS v4 geht diesen Weg konsequent — alle Design-Tokens werden als CSS Custom Properties in :root ausgegeben und können direkt im Stylesheet überschrieben werden.

Mironsoft

Design-Systeme, Theming-Architekturen und Tailwind CSS Projekte

Theming-System aufbauen statt Farben hardcoden?

Wir entwickeln skalierbare CSS-Theming-Architekturen mit Custom Properties, Design-Token-Systemen und automatisiertem Figma-Export — für Projekte, die heute wartbar und morgen erweiterbar sind.

Design-Token-System

Dreischichtige Token-Architektur mit Figma-Export und CSS Custom Properties

Dark Mode

CSS-only Dark Mode mit prefers-color-scheme und manuellem Toggle

Hyvä / Tailwind

Magento 2 Themes mit Tailwind CSS v4 und Custom Properties als Token-Layer

10. Zusammenfassung

CSS Custom Properties sind die Grundlage jedes modernen, wartbaren CSS-Theming-Systems. :root als globaler Token-Scope, semantische Schichten über rohen Werten und komponentenspezifische Local-API-Variablen bilden eine skalierbare Architektur. Dark Mode funktioniert ohne JavaScript über @media (prefers-color-scheme: dark) und Klassen-Toggle. @property erweitert das System um typisierte Properties, die animierbar sind und robustere Fallback-Mechanismen haben. Die dreischichtige Token-Architektur — roh, semantisch, komponentenspezifisch — hält Design-Entscheidungen konsistent und wartbar.

Der entscheidende Unterschied gegenüber Sass-Variablen bleibt die Laufzeitdynamik: CSS Custom Properties können per JavaScript geändert werden, kaskadieren durch den DOM und reagieren auf CSS-Selektoren. Das macht sie unverzichtbar für responsive, zugängliche und wartbare Design-Systeme. Tailwind CSS v4 demonstriert das pragmatisch: Alle Design-Tokens sind CSS Custom Properties, alle Anpassungen erfolgen per CSS-Override auf :root — kein Config-File, kein Build-Tool, nur CSS.

CSS Custom Properties — Das Wichtigste auf einen Blick

Dreischichtiges Token-System

Roh-Werte → Semantische Aliases → Komponentenspezifische Properties. Jede Schicht referenziert die darunter.

Dark Mode ohne JavaScript

@media (prefers-color-scheme: dark) überschreibt semantische Tokens auf :root. Klassen-Toggle für manuellen Switch.

@property Typisierung

syntax, inherits, initial-value machen Custom Properties animierbar und typsicher. Pflicht für Gradient-Animationen.

Component API

Lokale --component-Variablen als öffentliche Theming-Schnittstelle. Verwender überschreiben Properties, nicht Selektoren.

11. FAQ: CSS Custom Properties für Theming

1Was sind CSS Custom Properties?
Laufzeitvariablen mit -- Präfix, verwendet via var(). Im Browser, kaskadierend, per JavaScript änderbar — im Gegensatz zu Sass-Compile-Time-Konstanten.
2Custom Properties vs. Sass-Variablen?
Sass: compile-time, statisch, kein Browser-DOM. CSS Custom Properties: laufzeit, kaskadierend, per JavaScript änderbar.
3Dark Mode mit Custom Properties?
Semantische Farbvariablen auf :root, in @media (prefers-color-scheme: dark) überschreiben. Alle Komponenten reagieren automatisch. Kein JavaScript.
4Was macht @property?
Registriert Custom Property mit Typ (syntax), Vererbung (inherits) und Initialwert. Typisierte Properties können animiert werden.
5Fallback-Werte in var()?
var(--meine-var, fallback) — Fallback wenn Variable nicht definiert oder ungültig. Schachtelbar: var(--a, var(--b, literalwert)).
6Custom Properties per JavaScript ändern?
document.documentElement.style.setProperty('--var', 'wert') für globale Properties. element.style.setProperty() für lokale.
7Custom Properties animieren?
Nur mit @property und korrektem syntax-Typ. Ohne Typisierung springen Werte sofort statt zu interpolieren.
8Was ist Component-Theming?
Komponente definiert lokale --component-Variablen als öffentliche API. Verwender überschreiben diese, nicht interne Selektoren.
9Tailwind CSS v4 und Custom Properties?
Tailwind v4 gibt alle Design-Tokens als Custom Properties auf :root aus. Anpassungen per @theme oder direktem CSS-Override — kein Config-File nötig.
10Design-Token-System aufbauen?
Drei Schichten: Roh-Werte → Semantische Aliases → Komponentenspezifische Tokens. Jede Schicht referenziert die darunter.