Custom Properties ermöglichen uns nicht nur, unseren Code effizienter zu gestalten, sondern erlauben uns auch, echte Magie mit CSS zu vollbringen. Ein Bereich, in dem sie ein **riesiges** Potenzial haben, ist das Theming. Bei Atomic Smash verwenden wir Tailwind CSS, ein Utility-Class-Framework, zum Schreiben unserer Styles. In diesem Artikel werden wir untersuchen, wie Custom Properties für Theming verwendet werden können und wie wir sie mit Tailwind integrieren können, um die Wiederverwendbarkeit unseres Codes zu maximieren. Wir werden nicht behandeln, wie man Tailwind einrichtet – schauen Sie sich dafür die offizielle Dokumentation an – aber auch wenn Sie neu darin sind, könnten einige dieser Tipps nützlich sein.
Theming-Übersicht
Nehmen wir an, wir haben eine "Call To Action" (CTA) Komponente mit einer Überschrift, einem Haupttext und einem Button.

Das Schreiben von normalem (nicht-Tailwind) CSS für dieses Farbschema würde etwa so aussehen
.cta {
background-color: #742a2a; // dark red
color: #ffffff; //white
}
.cta__heading {
background-color: #e53e3e; // medium red
color: #742a2a;
}
.cta__button {
background-color: #e53e3e;
}
Mit Tailwind würden wir diese Farben als Utility-Klassen in unserem HTML anwenden
<div class="bg-red-900 text-white">
<h3 class="bg-red-600 text-red-900">Join our mailing list</h3>
<div>
<p>Be the first to hear about our new offerings</p>
<button class="bg-red-600" type="button">Sign up</button>
</div>
</div>
Ich habe bewusst Klassen ausgelassen, die sich auf etwas anderes als das grundlegende Farbschema beziehen, aber Sie können das vollständige Beispiel in dieser Demo sehen
Wenn wir nun ein anderes Farbschema auf unsere Komponente anwenden wollten, müssten wir die Farbwerte unserer ursprünglichen Komponente überschreiben. Ohne Tailwind wäre ein gängiger Weg, eine Theme-Klasse an die Komponente selbst anzuhängen und die Farbwerte weiter unten in der Kaskade neu zu definieren. Für eine Komponente mit einer Modifikator-Klasse .cta--blue (unter Verwendung der BEM-Konvention) wenden wir die CSS-Werte für ein blaues Farbschema an
.cta--blue {
background-color: #2a4365; // dark blue
}
.cta--blue .cta__heading {
background-color: #3182ce; // medium blue
color: #2a4365;
}
.cta--blue .cta__button {
background-color: #3182ce;
}

Wenn wir Sass oder einen anderen Präprozessor verwenden, ist es wahrscheinlich, dass wir uns das Leben erleichtern, indem wir Variablen für diese Farbnamen verwenden, und wir könnten die Selektoren .cta__heading und .cta__body verschachteln. Es macht unseren Code nicht gerade prägnanter, aber es macht ihn besser handhabbar, indem wir einen einzigen Ort haben, um diese Werte zu aktualisieren.
Angenommen, wir haben 10 verschiedene Farbschemata, wie es bei mir in einem kürzlichen Projekt der Fall war. Unser Code wird länger, da wir im Grunde das obige Beispiel 10 Mal duplizieren, um diese Farbwerte zu ändern. Stellen Sie sich nun vor, *jede* Komponente in unserem Designsystem benötigt 10 Farbschemata, und viele dieser Komponenten sind weitaus komplexer als unser einfaches CTA. Vielleicht benötigen unsere Themes auch unterschiedliche Schriftarten. Plötzlich haben wir *viel* CSS zu schreiben.
Theming mit Tailwind
Wenn wir hingegen Tailwind verwenden, müssten wir mehrere Klassen im HTML selbst ändern. Selbst wenn wir ein JavaScript-Framework wie React oder Vue verwenden, ist dies keine triviale Aufgabe. Um sicherzustellen, dass ungenutzte Stile in einem Produktions-Build entfernt werden, lehnt Tailwind die Verwendung von String-Verkettungen für Klassennamen ab (zum Zeitpunkt des Schreibens). Das Erstellen unserer Themes bedeutet also potenziell viel Logik in unsere Komponenten zu packen.
Theming mit Custom Properties
Durch die Verwendung von Custom Properties für unsere Farbthemen können wir die zu schreibende Code-Menge drastisch reduzieren und die Wartungsarbeit erleichtern. Schauen wir uns zunächst an, wie wir das in regulärem CSS machen können.
Wir definieren unsere Custom Properties als Variablen auf dem :root-Selektor und machen sie so zu globalen Variablen. (Der body-Selektor würde uns genauso gut dienen.) Dann können wir diese Variablen in einem Selektor anstelle unserer Farbwerte verwenden.
:root {
--primary: #742a2a; // dark red;
--secondary: #e53e3e; // medium red
}
.cta {
background-color: var(--primary);
color: white;
}
.cta__heading {
background-color: var(--secondary);
color: var(--primary);
}
.cta__button {
background-color: var(--secondary);
}
Hier passiert die eigentliche Magie: Der Code zur Erstellung jedes unserer Themes wird nun nur noch durch das Ändern dieser Custom Property-Werte erstellt. Die neuen Werte werden überall vererbt, wo wir unsere Theme-Klasse anwenden.
.th-blue {
--primary: #2a4365; // dark blue
--secondary: #3182ce; // medium blue
}
Wenn wir ein blaues Farbschema wünschen, können wir diese Klasse .th-blue auf die Komponente anwenden oder sie sogar auf das <body>-Tag anwenden, um ein seitenweites Theme zu verwenden, das bei einzelnen Komponenten nach Wunsch überschrieben werden kann. Die Verwendung einer Utility-Klasse erspart uns potenziell noch mehr Schreibarbeit im Vergleich zu einer komponenten-spezifischen Klasse (wie .cta--blue im ursprünglichen Code), da sie überall in unserer Codebasis angewendet werden könnte.
Unterstützung älterer Browser
Wie viele Agenturen müssen wir bei Atomic Smash immer noch Internet Explorer 11 unterstützen. Während ich in den meisten Fällen mit einem progressiven Enhancement-Ansatz einverstanden bin (indem ich z. B. einfachere Fallback-Layouts für Browser bereitstelle, die CSS Grid nicht unterstützen), finde ich, dass Theming ein Bereich ist, der oft keinen leichten Kompromiss zulässt. Kunden möchten ihre Markenfarben und Schriftarten sehen, auch in älteren Browsern. Das Bereitstellen von Fallbacks mit Feature Queries würde viel zusätzliche Arbeit bedeuten, die die Vorteile der Verwendung von Custom Properties von vornherein zunichtemachen würde. Um dies zu überwinden, benötigen wir ein Polyfill.
Es gibt einige Optionen für das Polyfilling von Custom Properties in IE 11.
postcss-custom-properties
Die erste ist die Verwendung eines PostCSS-Plugins namens postcss-custom-properties. Wenn Sie bereits PostCSS in Ihrem Workflow verwenden, ist dies recht einfach hinzuzufügen. Es funktioniert, indem es Ihr CSS verarbeitet und das Ergebnis der Variable als Eigenschaftswert ausgibt. Wenn Sie also das folgende CSS haben
:root {
--color: red;
}
h1 {
color: var(--color);
}
Das verarbeitete Ergebnis wird sein
h1 {
color: red;
color: var(--color);
}
Browser, die keine Custom Properties unterstützen, ignorieren die zweite Regel und greifen auf den normalen Eigenschaftswert zurück. Es gibt auch eine Option, die Regeln mit den Custom Properties in der Ausgabe zu entfernen, sodass die Dateigröße kleiner wird. Das bedeutet, dass *keine* Browser die Custom Property erhalten – was ein Problem ist, wenn Sie Variablen dynamisch aktualisieren – aber Sie können sie für statische Werte in Ihrem Code ohne negative Auswirkungen verwenden.
Leider hat dieses Polyfill einige Einschränkungen
- Sie müssen die Datei (oder Dateien) in Ihrer Konfiguration angeben, in der Sie die Custom Properties definieren.
- Custom Properties können *nur* auf dem
:root-Selektor definiert werden.
Die erste Einschränkung ist relativ trivial, aber die zweite macht dieses Polyfill für unseren Theming-Anwendungsfall leider völlig nutzlos. Das bedeutet, wir können keine Variablen auf einem Selektor neu definieren, um unsere Themes zu erstellen.
ie11CustomProperties
Diese Polyfill-Option beinhaltet das Ausliefern eines clientseitigen Skripts anstelle des Vorverarbeitens des CSS. Wir können das folgende Skript in unseren Head einfügen, um sicherzustellen, dass das Polyfill nur in IE 11 geladen wird
<script>window.MSInputMethodContext && document.documentMode && document.write('<script src="https://cdn.jsdelivr.net/gh/nuxodin/[email protected]/ie11CustomProperties.min.js"><\/script>');</script>
Dies ermöglicht es uns, die vollen Vorteile von Custom Properties wie in den hier gezeigten Beispielen zu nutzen, daher ist es die Lösung, für die ich mich entschieden habe. Es hat eine Einschränkung, dass in style-Attributen gesetzte Custom Properties nicht polyfilled werden. Aber ich habe es für das obige Theming-Beispiel getestet und es funktioniert einwandfrei.
Aber was hat das mit Tailwind zu tun?
Wie wir bereits gesehen haben, können Utility-Klassen – zweckgebundene Klassen, die überall in unserem HTML angewendet werden können – unseren Code wiederverwendbarer machen. Das ist das Hauptverkaufsargument von Tailwind und anderen Utility-Class-Frameworks – die Größe der CSS-Datei, die Sie ausliefern, sollte dadurch kleiner werden. Tailwind bietet mehrere Farbklassen an: .bg-red-medium würde uns einen roten background-color-Eigenschaftswert geben, .text-red-medium für color und so weiter für border, box-shadow oder überall dort, wo Sie einen Farbwert benötigen könnten.
Farben können in einer Konfigurationsdatei definiert werden
module.exports = {
theme: {
colors: {
red: {
medium: '#e53e3e',
dark: '#742a2a'
},
blue: {
medium: '#3182ce',
dark: '#2a4365'
}
}
}
}
Wenn wir Custom Property-Werte für unsere Tailwind-Klassen verwenden möchten, können wir sie in der Konfiguration angeben
module.exports = {
theme: {
colors: {
'th-primary': 'var(--primary)',
'th-secondary': 'var(--secondary)'
}
}
}
Ich präfixiere meine Farben und Theme-bezogenen Klassennamen mit th-, damit offensichtlich ist, dass sie speziell mit Theming zusammenhängen, aber Sie können gerne jede Konvention verwenden, die Ihnen gefällt.
Jetzt sind diese Klassen über Tailwind für uns verfügbar. Die Verwendung von .bg-th-primary liefert uns das Äquivalent von
.some-element {
background-color: var(--primary);
}
In unserem CSS können wir unsere Custom Properties für unsere Themes wie bisher definieren
:root {
--primary: #742a2a;
--secondary: #742a2a;
}
.th-blue {
--primary: #2a4365;
--secondary: #3182ce;
}
Wenden wir diese Klassen auf unser HTML an. Das erste Beispiel liefert uns eine Komponente mit unserem Standard-Theme (die Variablen, die auf :root definiert sind). Das zweite hat unser blaues Theme. Der einzige Unterschied ist die Hinzufügung der Klasse .th-blue zur Komponente. (Auch hier habe ich die vom Thema unabhängigen Klassen aus Gründen der Kürze und Klarheit weggelassen.)
<!--Component with default (red) theme-->
<div class="bg-th-primary">
<h3 class="bg-th-secondary text-th-primary">Join our mailing list</h3>
<div>
<p>Be the first to hear about our new offerings</p>
<button class="bg-th-secondary" type="button">Sign up</button>
</div>
</div>
<!--Component with blue theme-->
<div class="th-blue bg-th-primary">
<h3 class="bg-th-secondary text-th-primary">Join our mailing list</h3>
<div>
<p>Be the first to hear about our new offerings</p>
<button class="bg-th-secondary" type="button">Sign up</button>
</div>
</div>
Verwendung der Konfiguration als Styleguide
Tailwind ermutigt Sie, alle Variablen in der Konfiguration zu definieren, und persönlich stimme ich zu, dass dies ein besserer Ansatz ist. Das bedeutet, dass die Konfigurationsdatei eine einzige Quelle der Wahrheit sein kann, anstatt (potenziell) an mehreren Stellen Ihre Farben und andere Theme-Werte zu definieren. Glücklicherweise können wir auch Werte aus der Tailwind-Konfigurationsdatei für unsere Custom Properties verwenden. Wir müssen zunächst *alle* unsere Farben in der Konfiguration definieren (vorausgesetzt, wir verwenden nicht die Standardfarbpalette von Tailwind).
module.exports = {
theme: {
colors: {
red: {
medium: '#e53e3e',
dark: '#742a2a'
},
blue: {
medium: '#3182ce',
dark: '#2a4365'
},
'th-primary': 'var(--primary)',
'th-secondary': 'var(--secondary)'
}
}
}
Dann können wir das Theme-Objekt in CSS abrufen
:root {
--primary: theme('colors.red.dark');
--secondary: theme('colors.red.medium');
}
.th-blue {
--primary: theme('colors.blue.dark');
--secondary: theme('colors.blue.medium');
}
Zusammenfassung
Ich bin wirklich begeistert von den Vorteilen, Custom Properties nutzen zu können, ohne mir Gedanken über Browserunterstützung machen zu müssen, und noch mehr davon, sie nahtlos in unseren bestehenden Workflow integrieren zu können. Es ist schwer zu überschätzen, wie viel Zeit sie uns für Theming sparen werden. Ich hoffe, dass dieser Artikel Sie auch dann ermutigen wird, Custom Properties für diesen Anwendungsfall auszuprobieren, wenn Sie kein Tailwind-Benutzer sind.
Ich liebe, liebe, liebe das. Der letzte Schritt ist für mich der wichtigste, eine einzige Quelle der Wahrheit in der Konfigurationsdatei zu haben. Danke fürs Teilen. Wenn ich mich nur mit dem Wechsel von SCSS zu PostCSS auseinandersetzen könnte, dann könnte ich theme() so verwenden, wie es gedacht ist…
Eine Sache, die man beachten sollte, ist, dass Tailwind großartige Utils wie bg-opacity, text-opacity usw. hat, die nicht funktionieren werden.
Die Lösung besteht darin, Variablen wie
--red:255,0,0zu speichern und vielleicht Standard-Plugins neu zu schreiben, um dies zu tunLiebe diesen Artikel und diesen Ansatz, ich bin gespannt, ihn auszuprobieren. Danke für den Artikel, Michelle!
Toller Artikel! Entlarvt schön den Mythos, dass Tailwind nicht mit Custom Properties funktionieren kann. Ich bin nur ein wenig verwirrt über den letzten Schritt… sollten die in CSS definierten Variablen nicht –primary/–secondary statt –th-primary/–th-secondary heißen? Weil in der Tailwind-Konfiguration verwenden Sie var(–primary)/var(–secondary).
Ja, Sie haben Recht – das ist ein Tippfehler! Ich habe den Beitrag gerade aktualisiert.
Hallo Michelle,
Danke für diesen Artikel und Ihre Einblicke… sehr gute Informationen!
Ich bin ein neuer Tailwind-Benutzer. Auf meiner Lernreise habe ich ein Farb-Theming-Utility namens tw-themes veröffentlicht, das im Einklang mit Ihrem Artikel steht.
Sie finden die Dokumentation hier: https://tw-themes.js.org/
Es fördert dynamische Farbthemen, die zur Laufzeit wählbar sind. Das Beste daran ist, dass es Ihren Dark-Mode durch „Farbumkehrung“ automatisiert!
Wenn Sie Zeit haben, würde ich gerne Ihre Gedanken und/oder eine Empfehlung hören.
Vielen Dank im Voraus.