Farb-Theming in React: CSS-Variablen und Context
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://colorfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://colorfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://colorfyi.com/entity//)
Use the native HTML custom element.
Theming ist eines jener Probleme, das einfach aussieht, bis man es im großen Maßstab pflegen muss. Ein einzelner Umschalter zwischen hellem und dunklem Modus ist unkompliziert. Ein Produkt, das mehrere Marken bedient, benutzerdefinierbare Paletten anbietet oder Themes sofort ohne Seitenneuladung wechseln muss, erfordert eine durchdachtere Architektur.
Reacts Komponentenmodell und CSS-Custom-Properties sind eine natürliche Kombination für Theming. CSS-Variablen verwalten die Farbwerte deklarativ; React Context verwaltet den Theme-Zustand; und Tailwind CSS (in modernen Projekten) verbindet beides. Dieser Leitfaden behandelt jede Ebene – wie man die Theme-Architektur strukturiert, die CSS-Variablen-Grundlage implementiert, den React-Zustand verbindet und Multi-Brand-Szenarien handhabt.
Theme-Architektur-Muster
Muster 1: Nur CSS-Variablen (kein JavaScript-Zustand)
Das einfachste Muster für grundlegende Dark-Mode-Unterstützung benötigt keinen React-Zustand. Eine prefers-color-scheme-Media-Query ändert die Custom-Property-Werte, und alle Komponenten aktualisieren sich automatisch:
/* globals.css */
:root {
--color-bg: #F8FAFC;
--color-text: #1E293B;
--color-brand: #2563EB;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0F172A;
--color-text: #F1F5F9;
--color-brand: #60A5FA;
}
}
Komponenten verwenden var(--color-bg) und müssen nichts über das aktuelle Theme wissen. Der Browser erledigt alles.
Einsatz: Websites, auf denen man die Betriebssystem-Präferenz respektieren möchte und kein benutzergesteuertes Umschalten benötigt.
Einschränkung: Keine Möglichkeit für Benutzer, ihre BS-Einstellung innerhalb der App zu überschreiben.
Muster 2: Data-Attribute-Theme-Wechsel
Fügen Sie dem <html>-Element ein data-theme-Attribut hinzu, um das aktive Theme explizit und überschreibbar zu machen. Dies ist SSR-kompatibel, vermeidet den Flash des falschen Themes und lässt sich trivial mit localStorage-Persistenz kombinieren:
/* Standard: hell */
[data-theme="light"],
:root {
--color-bg: #F8FAFC;
--color-text: #1E293B;
--color-brand: #2563EB;
--color-surface: #FFFFFF;
--color-border: #E2E8F0;
}
/* Dunkel */
[data-theme="dark"] {
--color-bg: #0F172A;
--color-text: #F1F5F9;
--color-brand: #60A5FA;
--color-surface: #1E293B;
--color-border: #334155;
}
// Theme vor dem ersten Rendering anwenden (Inline-Skript in <head>)
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
Einsatz: Die meisten Produktionsanwendungen, die einen benutzergesteuerten Umschalter benötigen. Der Data-Attribute-Ansatz ist der aktuelle Industriestandard – verwendet von Tailwind CSS's Dark-Mode-Implementierung, Radix UI und den meisten modernen Design-Systemen.
Muster 3: Klassenbasierter Theme-Wechsel
Ähnlich wie das Data-Attribut, aber mit CSS-Klassen. Tailwinds darkMode: 'class'-Konfiguration basiert auf diesem Muster:
.dark {
--color-bg: #0F172A;
--color-text: #F1F5F9;
}
Das Hinzufügen der dark-Klasse zu <html> aktiviert die Dark-Token-Werte. Tailwind wendet dann dark:-Präfix-Utilities an, wenn diese Klasse vorhanden ist.
Einsatz: Projekte, die Tailwind CSS mit der class-Dark-Mode-Strategie verwenden.
CSS-Variablen für Farben: Das Token-System
Semantische Benennung statt deskriptiver Benennung
Die wichtigste Entscheidung in einem Token-System ist die Benennung. Nach Farbwert benennen (--blue-500, --gray-900) macht die Variable isoliert verständlich, aber nicht theme-fähig – --blue-500 zu Lila zu ändern, bricht die Namens-Semantik.
Nach semantischer Rolle benennen (--color-brand, --color-text-muted) erlaubt es, Werte über Themes hinweg vollständig zu ändern, während Komponenten korrekt bleiben:
:root {
/* ---- Farb-Primitive (nicht direkt in Komponenten verwenden) ---- */
--blue-500: #3B82F6;
--blue-700: #1D4ED8;
--slate-50: #F8FAFC;
--slate-800: #1E293B;
--slate-900: #0F172A;
/* ---- Semantische Tokens (in Komponenten verwenden) ---- */
/* Text */
--text-primary: var(--slate-800);
--text-secondary: #64748B;
--text-disabled: #94A3B8;
--text-inverse: var(--slate-50);
/* Oberflächen */
--bg-base: var(--slate-50);
--bg-elevated: #FFFFFF;
--bg-sunken: #F1F5F9;
/* Marke */
--brand: var(--blue-700);
--brand-hover: #1E40AF;
--brand-subtle: #EFF6FF;
--on-brand: #FFFFFF;
/* Feedback */
--error: #DC2626;
--error-bg: #FEF2F2;
--success: #16A34A;
--success-bg: #F0FDF4;
--warning: #D97706;
--warning-bg: #FFFBEB;
}
[data-theme="dark"] {
--text-primary: #F1F5F9;
--text-secondary: #94A3B8;
--text-disabled: #475569;
--text-inverse: #0F172A;
--bg-base: #0F172A;
--bg-elevated: #1E293B;
--bg-sunken: #0D1426;
--brand: #60A5FA;
--brand-hover: #93C5FD;
--brand-subtle: #172554;
--on-brand: #0F172A;
--error: #F87171;
--error-bg: #1C0A0A;
--success: #4ADE80;
--success-bg: #052E16;
--warning: #FCD34D;
--warning-bg: #1C1000;
}
Das Zwei-Ebenen-System (Primitive + semantische Tokens) bietet das Beste aus beiden Welten: eine rohe Farbpalette als Referenz und semantische Tokens für die Komponentennutzung.
Ihre Farbskala generieren
Bevor Sie das Token-System aufbauen, benötigen Sie die rohe Farbpalette. Der Shade Generator erzeugt aus einer einzigen Markenfarbe eine vollständige 50–950-Skala – dasselbe Skalenmuster wie Tailwind CSS. Geben Sie Ihren Marken-Hex-Code ein und erhalten Sie den vollständigen Satz von dunkel-zu-hell-Varianten, bereit als primitive Tokens.
Wenn Sie beispielsweise #2563EB als Ihre Marken-Blaufarbe eingeben, erhalten Sie:
blue-50: #EFF6FFblue-100: #DBEAFEblue-500: #3B82F6blue-700: #1D4ED8blue-900: #1E3A8Ablue-950: #172554
Diese primitiven Werte füllen dann Ihre semantischen Tokens, wodurch sichergestellt wird, dass Ihre Dark-Mode-Farben harmonisch mit Ihren Light-Mode-Farben verwandt sind – statt willkürlicher dunkler Annäherungen.
React Context für den Theme-Zustand
Das ThemeContext-Muster
Für Apps mit einem benutzergesteuerten Theme-Umschalter stellt React Context die Zustandsverwaltungsebene bereit. Der Context speichert den aktiven Theme-Namen und stellt eine Umschalt-Funktion bereit:
// contexts/ThemeContext.tsx
import { createContext, useContext, useEffect, useState } from 'react';
type Theme = 'light' | 'dark' | 'system';
interface ThemeContextValue {
theme: Theme;
resolvedTheme: 'light' | 'dark'; // Tatsächlich angewendetes Theme (löst 'system' auf)
setTheme: (theme: Theme) => void;
}
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setThemeState] = useState<Theme>(() => {
if (typeof window === 'undefined') return 'system';
return (localStorage.getItem('theme') as Theme) || 'system';
});
const systemPrefersDark = typeof window !== 'undefined'
? window.matchMedia('(prefers-color-scheme: dark)').matches
: false;
const resolvedTheme: 'light' | 'dark' =
theme === 'system'
? (systemPrefersDark ? 'dark' : 'light')
: theme;
useEffect(() => {
const root = document.documentElement;
root.setAttribute('data-theme', resolvedTheme);
localStorage.setItem('theme', theme);
}, [theme, resolvedTheme]);
// Auf Systemeinstellungsänderungen lauschen, wenn theme === 'system'
useEffect(() => {
if (theme !== 'system') return;
const media = window.matchMedia('(prefers-color-scheme: dark)');
const handler = () => {
document.documentElement.setAttribute(
'data-theme',
media.matches ? 'dark' : 'light'
);
};
media.addEventListener('change', handler);
return () => media.removeEventListener('change', handler);
}, [theme]);
const setTheme = (newTheme: Theme) => {
setThemeState(newTheme);
};
return (
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme(): ThemeContextValue {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme muss innerhalb von ThemeProvider verwendet werden');
return ctx;
}
Der ThemeProvider umschließt Ihre Anwendung (oder den relevanten Teilbaum) und der useTheme-Hook stellt den Theme-Zustand überall im Komponentenbaum bereit:
// components/ThemeToggle.tsx
import { useTheme } from '../contexts/ThemeContext';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
aria-label={`Zu ${theme === 'dark' ? 'hellem' : 'dunklem'} Modus wechseln`}
style={{
background: 'var(--bg-elevated)',
color: 'var(--text-primary)',
border: '1px solid var(--color-border)',
padding: '8px 16px',
borderRadius: '8px',
cursor: 'pointer',
}}
>
{theme === 'dark' ? '☀ Hell' : '☾ Dunkel'}
</button>
);
}
Komponenten verwenden CSS-Custom-Properties für ihre visuellen Stile – sie müssen resolvedTheme nicht lesen, um Farben anzuwenden. Der Context wird nur für UI benötigt, die den aktuellen Theme-Zustand anzeigt oder steuert.
Flash des falschen Themes verhindern (FOTWT)
Server-seitig gerenderte Apps stehen vor dem Problem des falschen Theme-Aufblitzens: Der Server rendert HTML, ohne die Theme-Präferenz des Benutzers zu kennen, der Browser zeigt das HTML kurz an, dann hydratisiert React und wendet das richtige Theme an – was einen sichtbaren Aufblitz verursacht.
Die Lösung ist ein blockierendes Inline-Skript in <head>, das localStorage liest und das Theme-Attribut setzt, bevor die Seite gerendert wird:
<!-- In Ihrem <head>, vor CSS-Links -->
<script>
(function() {
try {
var stored = localStorage.getItem('theme');
var systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
var theme = stored === 'dark' || stored === 'light'
? stored
: (systemDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
} catch(e) {}
})();
</script>
In Next.js gehört dies in eine _document.tsx oder in das layout.tsx des App Routers. Da dieses Skript synchron läuft, bevor CSS geparst wird, gibt es keinen Aufblitz.
Tailwind CSS Themes
Tailwind v3: Klassenbasierter Dark Mode
In Tailwind v3 erfordert Dark Mode die Einstellung darkMode: 'class' in tailwind.config.js. Dadurch wendet Tailwind dark:-Utilities nur an, wenn die .dark-Klasse auf dem <html>-Element ist:
// tailwind.config.js
module.exports = {
darkMode: 'class',
theme: {
extend: {
colors: {
brand: {
50: '#EFF6FF',
100: '#DBEAFE',
500: '#3B82F6',
600: '#2563EB',
700: '#1D4ED8',
900: '#1E3A8A',
950: '#172554',
},
},
},
},
};
Komponenten verwenden dark:-Präfixe für Dark-Mode-Varianten:
<div className="bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100">
<button className="bg-brand-600 dark:bg-brand-500 text-white hover:bg-brand-700 dark:hover:bg-brand-400">
Primäre Aktion
</button>
</div>
Der Theme-Umschalter setzt die .dark-Klasse auf document.documentElement – derselbe Ansatz wie das Data-Attribut-Muster, nur mit einer Klasse statt eines Attributs.
Tailwind v4: CSS-First-Konfiguration
Tailwind v4 verschiebt die Konfiguration vollständig in CSS und verwendet nativ CSS-Custom-Properties:
/* styles.css */
@import "tailwindcss";
@theme {
--color-brand-50: #EFF6FF;
--color-brand-100: #DBEAFE;
--color-brand-500: #3B82F6;
--color-brand-600: #2563EB;
--color-brand-700: #1D4ED8;
}
Tailwind v4 generiert Utility-Klassen aus diesen Custom-Properties, die sofort in JSX verwendbar sind: className="bg-brand-600 text-white". Die Dark-Mode-Konfiguration in v4 verwendet @variant dark:
@variant dark (&:where([data-theme="dark"] *)) {
/* Tailwind wendet dark:-Utilities basierend auf diesem Selektor an */
}
Diese Variante weist Tailwind an, dark:-Utilities anzuwenden, wenn ein Vorfahrenelement data-theme="dark" hat, und integriert sich nativ in das Data-Attribut-Muster.
Multi-Brand-Theming-Strategie
Die Herausforderung
Eine SaaS-Plattform, die mehrere Kunden bedient, ein White-Label-Produkt oder ein Design-System, das für mehrere Produktmarken geteilt wird, muss nicht nur hell/dunkel-Varianten, sondern vollständig unterschiedliche Farbidentitäten handhaben – jede mit eigenem Marken-Primär, Akzent, Status-Farben und Neutralen.
Marken-Tokens als CSS-Variablen-Überschreibungen
Der wartbarste Ansatz definiert markenagnostische semantische Tokens in einem Basis-Stylesheet und überschreibt dann Primitiv-Zuweisungen pro Marke:
/* Basis-Theme – dieselben Token-Namen für alle Marken */
:root {
/* Marken-Primitive – pro Marke überschrieben */
--brand-primary-raw: 37 99 235; /* #2563EB in RGB-Kanälen */
--brand-accent-raw: 99 102 241; /* #6366F1 in RGB-Kanälen */
/* Semantische Tokens – aus Primitiven berechnet */
--brand-primary: rgb(var(--brand-primary-raw));
--brand-primary-hover: color-mix(in srgb, rgb(var(--brand-primary-raw)) 80%, black);
--brand-primary-subtle: color-mix(in srgb, rgb(var(--brand-primary-raw)) 10%, white);
}
/* Marke: Acme Corp (Blau) */
[data-brand="acme"] {
--brand-primary-raw: 37 99 235; /* #2563EB */
--brand-accent-raw: 16 185 129; /* #10B981 */
}
/* Marke: Globex Inc (Lila) */
[data-brand="globex"] {
--brand-primary-raw: 124 58 237; /* #7C3AED */
--brand-accent-raw: 245 158 11; /* #F59E0B */
}
/* Marke: Initech (Grün) */
[data-brand="initech"] {
--brand-primary-raw: 22 163 74; /* #16A34A */
--brand-accent-raw: 239 68 68; /* #EF4444 */
}
Die Technik mit rohen RGB-Kanälen (37 99 235) ermöglicht Opazitätsvarianten ohne zusätzliche Tokens:
.overlay {
background-color: rgba(var(--brand-primary-raw), 0.1);
}
React Multi-Brand Context
Erweitern Sie den Theme-Context zur Handhabung von Marken:
// contexts/ThemeContext.tsx
type Brand = 'acme' | 'globex' | 'initech' | 'default';
type Theme = 'light' | 'dark';
interface ThemeContextValue {
theme: Theme;
brand: Brand;
setTheme: (theme: Theme) => void;
setBrand: (brand: Brand) => void;
}
export function ThemeProvider({ initialBrand = 'default', children }: {
initialBrand?: Brand;
children: React.ReactNode;
}) {
const [theme, setTheme] = useState<Theme>('light');
const [brand, setBrand] = useState<Brand>(initialBrand);
useEffect(() => {
const root = document.documentElement;
root.setAttribute('data-theme', theme);
root.setAttribute('data-brand', brand);
}, [theme, brand]);
return (
<ThemeContext.Provider value={{ theme, brand, setTheme, setBrand }}>
{children}
</ThemeContext.Provider>
);
}
Setzen Sie die initiale Marke aus Ihrer Routing- oder Authentifizierungsebene – die Subdomain oder Mandanten-ID jedes Kunden wird einem Markenbezeichner zugeordnet.
Markenskalen mit dem Shade Generator erstellen
Für jede Marke benötigen Sie eine vollständige Farbskala, nicht nur ein oder zwei Hex-Werte. Verwenden Sie den Shade Generator, um eine 50–950-Skala für die Primär- und Akzentfarbe jeder Marke zu generieren. Geben Sie das primäre Hex der Marke ein und erhalten Sie die vollständige Palette von dunkel-zu-hell-Varianten.
Für Globex Inc mit Primär #7C3AED erzeugt der Shade Generator die vollständige Lila-Skala. Für Initech mit #16A34A die vollständige Grün-Skala. Diese Skalen füllen dann die primitiven Tokens für jede Marke und stellen sicher, dass --brand-primary-subtle (der hellste Tint) und --brand-primary-hover (ein dunklerer Druckzustand) innerhalb jeder Markenidentität harmonisch konsistent bleiben.
Wichtigste Erkenntnisse
- CSS-Custom-Properties sind die Grundlage: Definieren Sie semantische Tokens (
--text-primary,--brand-primary) und ändern Sie deren Werte pro Theme, anstatt in jeder Komponente bedingt Klassen anzuwenden. - Data-Attribute-Theme-Wechsel (
data-theme="dark") ist das flexibelste Muster – durch JavaScript überschreibbar, SSR-kompatibel und kombinierbar mitprefers-color-scheme-Media-Queries. - Das Zwei-Ebenen-Token-System (primitive Farbskala + semantische Rollen-Tokens) ermöglicht vollständige Theme-Flexibilität, während Komponenten sauber bleiben: Primitive definieren Ihre Palette, Semantik definiert, wie Farben verwendet werden.
- Verwenden Sie den Shade Generator, um eine vollständige 50–950-Farbskala aus Ihrer Marken-Primärfarbe zu generieren – das liefert die vollständige Palette heller und dunkler Varianten, die für ein durchdachtes Token-System benötigt wird.
- React Context verwaltet den Theme-Zustand (den aktiven Theme-Namen) und Seiteneffekte (das Data-Attribut setzen, in localStorage persistieren); Komponenten müssen den Context nie lesen, um die richtigen Farben anzuwenden.
- Den Flash des falschen Themes verhindern mit einem blockierenden Inline-Skript in
<head>, daslocalStorageliest und das Theme-Attribut setzt, bevor der Browser Pixel rendert. - Tailwind CSS-Integration: v3 verwendet
darkMode: 'class'mitdark:-Präfixen; v4 verwendet CSS-First-@theme-Konfiguration mit nativer CSS-Variablen-Unterstützung. - Multi-Brand-Theming stapelt ein weiteres Data-Attribut (
data-brand) auf das Theme-System und überschreibt primitive Token-Werte pro Marke, während semantische Tokens und Komponenten unverändert bleiben.