Leitfaden für CSS Custom Properties

Avatar of Chris Coyier
Chris Coyier am

Alles Wichtige und Nützliche, was man über CSS Custom Properties wissen sollte. Zum Beispiel, dass sie oft als „CSS Variables“ bezeichnet werden, obwohl das nicht ihr richtiger Name ist.

Präsentiert von DigitalOcean

DigitalOcean bietet die Cloud-Computing-Dienste, die Sie benötigen, um Ihr Wachstum in jeder Phase zu unterstützen. Starten Sie mit einem kostenlosen Guthaben von 200 $!

Eine Custom Property wird am häufigsten als Variable in CSS betrachtet.

.card {
  --spacing: 1.2rem;
  padding: var(--spacing);
  margin-bottom: var(--spacing);
}

Oben ist --spacing die Custom Property mit dem Wert 1.2rem, und var(--spacing) ist die verwendete Variable.

Der vielleicht wertvollste Grund, sie zu verwenden: sich nicht zu wiederholen (DRY-Code). Im obigen Beispiel kann ich den Wert 1.2rem an einer Stelle ändern und damit zwei Dinge beeinflussen. Das bringt etwas aus Programmiersprachen in CSS.

Es gibt einiges über Custom Properties zu wissen, also legen wir los.

Warum sind CSS Custom Properties wichtig?

  1. Sie helfen, dein CSS zu entfeuchten (DRY = Don’t Repeat Yourself). Custom Properties können Code einfacher warten, da du einen Wert an einer Stelle aktualisieren kannst und dieser sich an mehreren Stellen widerspiegelt. Sei jedoch vorsichtig, denn übermäßige Abstraktion kann den gegenteiligen Effekt haben und den Code unverständlicher machen.
  2. Sie sind besonders hilfreich für Dinge wie die Erstellung von Farbthemen auf einer Website.
  3. Sie eröffnen interessante Möglichkeiten in CSS. Nicht zuletzt, weil sie kaskadieren.
  4. Die Tatsache, dass sie in JavaScript aktualisiert werden können, eröffnet noch interessantere Möglichkeiten.

Benennung von Custom Properties

Custom Properties müssen sich innerhalb eines Selektors befinden und mit zwei Bindestrichen (--) beginnen.

/* Nope, not within a selector */
--foo: 1;

body {
  /* No, 0 or 1 dash won't work */
  foo: 1;
  -foo: 1; 

  /* Yep! */
  --foo: 1;

  /* OK, but they're different properties */
  --FOO: 1;
  --Foo: 1;
  
  /* Totally fine */
  --mainColor: red;
  --main-color: red;

  /* Special characters are a no */
  --color@home: red;
  --black&blue: black;
  --black^2: black;
}

Es ist am besten, sich an Buchstaben, Zahlen und Bindestriche zu halten und sicherzustellen, dass die Custom Property innerhalb eines gültigen Selektors definiert ist.

Properties als Properties

Du kannst den Wert einer Custom Property mit einer anderen Custom Property festlegen.

html {
  --red: #a24e34;
  --green: #01f3e6;
  --yellow: #f0e765;

  --error: var(--red);
  --errorBorder: 1px dashed var(--red);
  --ok: var(--green);
  --warning: var(--yellow);
}

Manche Leute machen das gerne so, weil es ermöglicht, dass der Name einer Custom Property beschreibend ist und dann in einer anderen Property mit einem funktionaleren Namen verwendet wird, was wiederum hilft, den Code DRY zu halten. Es kann sogar dazu beitragen, die funktionalen Namen lesbarer und verständlicher zu machen.

Es gibt einen großen Fallstrick bei Custom Properties, die andere Custom Properties verwenden, den du beachten solltest.

Gültige Werte für Custom Properties

Custom Properties sind überraschend tolerant, wenn es um die Werte geht, die sie akzeptieren.

Hier sind einige grundlegende Beispiele, von denen man erwarten würde, dass sie funktionieren, und das tun sie auch.

body {
  --brand-color: #990000;
  --transparent-black: rgba(0, 0, 0, 0.5);
  
  --spacing: 0.66rem;
  --max-reading-length: 70ch;
  --brandAngle: 22deg;

  --visibility: hidden;
  --my-name: "Chris Coyier";
}

Siehst du? Es können Hex-Werte, Farbfunktionen, Einheiten aller Art und sogar Textzeichenfolgen sein.

Aber Custom Properties müssen nicht unbedingt vollständige Werte wie diese sein. Schauen wir uns an, wie nützlich es sein kann, gültige CSS-Werte in Teile aufzuteilen, die wir in Custom Properties stecken können.

Aufteilen von Werten

Du kannst Custom Properties verwenden, um mehrteilige Werte aufzuteilen.

Stellen wir uns vor, du verwendest eine Farbfunktion, sagen wir rgba(). Jeder Farbkanalwert darin kann seine eigene Custom Property sein. Das eröffnet eine Menge Möglichkeiten, wie das Ändern des Alpha-Werts für einen bestimmten Anwendungsfall oder das Erstellen von Farbthemen.

Aufteilen von Farben

Nimm zum Beispiel HSL-Farben. Wir können sie in Teile aufteilen und dann die Teile, die wir wollen, sehr einfach anpassen. Vielleicht arbeiten wir mit der Hintergrundfarbe eines Buttons. Wir können bestimmte Teile seines HSL-Aufbaus aktualisieren, wenn der Button gehovert wird, fokussiert ist oder deaktiviert ist, ohne überhaupt background für einen dieser Zustände zu deklarieren.

button {
  --h: 100;
  --s: 50%;
  --l: 50%;
  --a: 1;

  background: hsl(var(--h) var(--s) var(--l) / var(--a));
}
button:hover { /* Change the lightness on hover */
  --l: 75%;
}
button:focus { /* Change the saturation on focus */
  --s: 75%;
}
button[disabled] {  /* Make look disabled */
  --s: 0%;
  --a: 0.5;
}

Indem wir Werte wie diesen aufteilen, können wir Teile davon steuern, auf eine Weise, wie wir es vorher nie konnten. Schau dir an, wie wir nicht alle HSL-Argumente deklarieren mussten, um den Hover-, Fokus- und Deaktivierungszustand eines Buttons zu stylen. Wir haben einfach spezifische HSL-Werte überschrieben, wenn wir sie brauchten. Ziemlich cool!

Schatten

box-shadow hat keine Shorthand-Eigenschaft, um die Ausdehnung des Schattens separat zu steuern. Aber wir könnten den box-shadow Ausdehnungswert herauslösen und ihn als Custom Property steuern (Demo).

button {
  --spread: 5px;
  box-shadow: 0 0 20px var(--spread) black;
}
button:hover {
  --spread: 10px;
}

Verläufe

Es gibt keine background-gradient-angle (oder ähnliche) Shorthand für Farbverläufe. Mit Custom Properties können wir nur diesen Teil ändern, als gäbe es so etwas.

body {
  --angle: 180deg;
  background: linear-gradient(var(--angle), red, blue);
}
body.sideways {
  --angle: 90deg;
}

Durch Kommas getrennte Werte (wie bei Hintergründen)

Jede Property, die mehrere durch Kommas getrennte Werte unterstützt, könnte ebenfalls ein guter Kandidat für das Aufteilen von Werten sein, da es keine Möglichkeit gibt, nur einen Wert aus einer durch Kommas getrennten Liste anzusprechen und ihn allein zu ändern.

/* Lots of backgrounds! */
background-image:
  url(./img/angles-top-left.svg),
  url(./img/angles-top-right.svg),
  url(./img/angles-bottom-right.svg),
  url(./img/angles-bottom-left.svg),
  url(./img/bonus-background.svg);

Angenommen, du möchtest nur einen von vielen Hintergründen bei einer Media Query entfernen. Du könntest das mit Custom Properties so machen, was es zu einer trivialen Aufgabe macht, Hintergründe auszutauschen oder zu überschreiben.

body {
  --bg1: url(./img/angles-top-left.svg);
  --bg2: url(./img/angles-top-right.svg);
  --bg3: url(./img/angles-bottom-right.svg);
  --bg4: url(./img/angles-bottom-left.svg);
  --bg5: url(./img/bonus-background.svg);
  
  background-image: var(--bg1), var(--bg2), var(--bg3), var(--bg4);
}
@media (min-width: 1500px) {
  body {
    background-image: var(--bg1), var(--bg2), var(--bg3), var(--bg4), var(--bg5);
  }
}

Grids

Wir sind gerade in Schwung, also machen wir noch ein paar Beispiele. Zum Beispiel können wir die Eigenschaft grid-template-columns nehmen und ihre Werte in Custom Properties abstrahieren, um ein super flexibles Grid-System zu erstellen.

.grid {
  display: grid;
  --edge: 10px;
  grid-template-columns: var(--edge) 1fr var(--edge);
}
@media (min-width: 1000px) {
  .grid {
     --edge: 15%;
   }
}

Transforms

CSS wird bald individuelle Transforms bekommen, aber wir können sie mit Custom Properties früher bekommen. Die Idee ist, alle Transforms, die ein Element erhalten könnte, im Voraus anzuwenden und sie dann bei Bedarf einzeln zu steuern.

button {
  transform: var(--scale, scale(1)) var(--translate, translate(0));
}
button:active {
  --translate: translate(0, 2px);
}
button:hover {
  --scale: scale(0.9);
}

Verkettung von Einheitentypen

Es gibt Zeiten, in denen das Kombinieren von Teilen von Werten nicht so funktioniert, wie man es sich erhofft. Du kannst zum Beispiel nicht 24px erstellen, indem du 24 und px zusammenfügst. Das ist jedoch möglich, indem du die rohe Zahl mit einem Zahlenwert mit einer Einheit multiplizierst.

body {
  --value: 24;
  --unit: px;
  
  /* Nope */
  font-size: var(--value) + var(--unit);
  
  /* Yep */
  font-size: calc(var(--value) * 1px);

  /* Yep */
  --pixel_converter: 1px;
  font-size: calc(var(--value) * var(--pixel_converter));
}

Verwendung der Kaskade

Die Tatsache, dass Custom Properties die Kaskade verwenden, ist eine ihrer nützlichsten Eigenschaften.

Du hast es bereits in vielen der Beispiele gesehen, die wir behandelt haben, aber lass es uns noch einmal verdeutlichen. Angenommen, wir haben eine Custom Property, die „sehr weit oben“ (am Body) festgelegt ist, und dann erneut für eine bestimmte Klasse festgelegt wird. Wir verwenden sie auf einer bestimmten Komponente.

body {
  --background: white;
}
.sidebar {
  --background: gray;
}
.module {
  background: var(--background);
}

Dann haben wir praktisches HTML wie dieses:

<body> <!-- --background: white -->

  <main>
    <div class="module">
      I will have a white background.
    </div>
  <main>

  <aside class="sidebar"> <!-- --background: gray -->
    <div class="module">
      I will have a gray background.
    </div>
  </aside>

</body>
Three CSS rulesets, one for a body, sidebar and module. the background custom property is defined as white on body and gray on sidebar. The module calls the custom property and shows an orange arrow pointing to the custom property defined in the sidebar since it is the nearest ancestor.
Für das zweite Modul ist .sidebar ein näherer Vorfahre als body, daher löst --background dort zu gray auf, aber an anderen Stellen zu weiß.

Das „Modul“ in der Sidebar hat einen grauen Hintergrund, weil Custom Properties (wie viele andere CSS-Eigenschaften) durch die HTML-Struktur vererbt werden. Jedes Modul nimmt den --background-Wert vom nächsten „Vorfahren“, bei dem er in CSS definiert wurde.

Wir haben also eine CSS-Deklaration, die in verschiedenen Kontexten unterschiedliche Dinge tut, dank der Kaskade. Das ist einfach cool.

Das spielt sich auch auf andere Weise ab:

button {
  --foo: Default;
}
button:hover {
  --foo: I win, when hovered;
  /* This is a more specific selector, so re-setting 
     custom properties here will override those in `button` */
}

Media Queries ändern nicht die Spezifität, aber sie kommen oft später (oder weiter unten) in der CSS-Datei als der ursprüngliche Selektor, der einen Wert festlegt, was auch bedeutet, dass eine Custom Property innerhalb der Media Query überschrieben wird.

body {
  --size: 16px;
  font-size: var(--size);
}
@media (max-width: 600px) {
  body {
    --size: 14px;
  } 
}

Media Queries sind nicht nur für Bildschirmgrößen. Sie können für Dinge wie Barrierefreiheitseinstellungen verwendet werden. Zum Beispiel Dark Mode.

body {
  --bg-color: white; 
  --text-color: black;

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

/* If the user's preferred color scheme is dark */
@media screen and (prefers-color-scheme: dark) {
  body {
    --bg-color: black;
    --text-color: white;
  }
}

Die :root-Sache

Man sieht oft, dass Custom Properties „am Root“ festgelegt werden. Das bedeutet Folgendes:

:root {
  --color: red;
}

/* ...is largely the same as writing: */
html {
  --color: red;
}

/* ...except :root has higher specificity, so remember that! */

Es gibt keinen besonders zwingenden Grund, Custom Properties so zu definieren. Es ist einfach eine Möglichkeit, Custom Properties so weit oben wie möglich festzulegen. Wenn du das magst, ist das völlig in Ordnung. Ich finde es irgendwie normaler, sie auf die html- oder body-Selektoren anzuwenden, wenn ich Eigenschaften festlege, die ich global oder überall verfügbar machen möchte.

Es gibt auch keinen Grund, warum du Variablen in diesem breiten Geltungsbereich festlegen musst. Es kann genauso nützlich und vielleicht lesbarer und verständlicher sein, sie genau auf der Ebene festzulegen, auf der du sie verwenden wirst (oder ziemlich nah im DOM-Baum).

.module {
  --module-spacing: 1rem;
  --module-border-width: 2px;

  border: var(--module-border-width) solid black;
}

.module + .module {
  margin-top: var(--module-spacing);
}

Beachte, dass das Festlegen einer Custom Property am Modul selbst bedeutet, dass diese Property nicht mehr von einem Vorfahren erbt (es sei denn, wir setzen den Wert auf inherit). Wie bei anderen vererbten Eigenschaften gibt es manchmal Gründe, sie an Ort und Stelle (auf globaler Ebene) festzulegen, und manchmal möchten wir sie vom Kontext (auf Komponentenebene) erben. Beides ist nützlich. Das Coole an Custom Properties ist, dass wir sie an einer Stelle definieren, sie hinter den Kulissen vererben und sie an einer völlig anderen Stelle anwenden können. Wir übernehmen die Kontrolle über die Kaskade!

Kombination mit !important

Du kannst einen !important-Modifikator innerhalb oder außerhalb einer Variablen setzen.

.override-red {
  /* this works */
  --color: red !important;  
  color: var(--color);

  /* this works, too */
  --border: red;
  border: 1px solid var(--border) !important;
}

Das Anwenden von !important auf die Variable --color macht es schwierig, den Wert der Variable --color zu überschreiben, aber wir können ihn immer noch ignorieren, indem wir die Eigenschaft color ändern.

Das Verhalten von !important innerhalb der Werte von Custom Properties ist ziemlich ungewöhnlich. Stefan Judis dokumentiert es gut, aber im Grunde:

  1. Letztendlich wird !important aus dem Wert der Custom Property entfernt.
  2. Aber es wird verwendet, um zu bestimmen, welcher Wert gewinnt, wenn er an mehreren Stellen festgelegt wird.
div {
  --color: red !important;
}
#id {
  --color: yellow;
}

Wenn beide Selektoren auf ein Element angewendet werden, könnte man denken, dass der Wert von #id wegen der höheren Spezifität gewinnt, aber tatsächlich gewinnt red wegen des !important, wird aber letztendlich ohne das !important angewendet. Es ist etwas verwirrend.

Wenn man das !important außerhalb der Custom Property anwendet, wie im 2. Beispiel zwei Codeblöcke weiter oben, bleibt unsere --border-Variable von geringer Spezifität (leicht zu überschreiben), aber es ist schwierig, die Art und Weise zu ändern, wie dieser Wert auf den border selbst angewendet wird, da die gesamte Deklaration !important beibehält.

Fallback-Werte für Custom Properties

Die Funktion var() ermöglicht Fallback-Werte in Custom Properties.

Hier legen wir eine scale()-Transformationsfunktion für eine Custom Property fest, aber es gibt einen durch Kommas getrennten zweiten Wert von 1.2. Dieser Wert 1.2 wird verwendet, wenn --scale nicht festgelegt ist.

.bigger {
  transform: scale(var(--scale, 1.2));
}

Nach dem ersten Komma sind alle weiteren Kommas Teil des Fallback-Werts. Das ermöglicht uns, Fallbacks mit durch Kommas getrennten Werten darin zu erstellen. Wir können zum Beispiel eine Variable haben, die auf einen ganzen Stapel von Schriftarten zurückfällt:

html {
  font-family: var(--fonts, Helvetica, Arial, sans-serif);
}

Wir können auch eine Reihe von Variablen-Fallbacks bereitstellen (so viele wir wollen), aber wir müssen sie verschachteln, damit das funktioniert:

.bigger {
  transform: scale(var(--scale, var(--second-fallback, 1.2));
}

Wenn --scale undefiniert ist, versuchen wir --second-fallback. Wenn auch das undefiniert ist, greifen wir schließlich auf 1.2 zurück.

Verwendung von calc() und Custom Properties

Noch mehr Leistung von Custom Properties wird freigeschaltet, wenn wir sie mit Mathematik kombinieren!

Diese Art von Dingen ist üblich:

main {
  --spacing: 2rem;
}

.module {
  padding: var(--spacing);
}

.module.tight {
  /* divide the amount of spacing in half */
  padding: calc(var(--spacing) / 2)); 
}

Wir könnten das auch verwenden, um den Farbton einer Komplementärfarbe zu berechnen:

html {
  --brand-hue: 320deg;
  --brand-color: hsl(var(--brand-hue), 50%, 50%);
  --complement: hsl(calc(var(--brand-hue) + 180deg), 50%, 50%);
}

calc() kann sogar mit mehreren Custom Properties verwendet werden:

.slider {
  width: calc(var(--number-of-boxes) * var(--width-of-box));
}

Verzögern des calc()

Es mag seltsam aussehen, eine kalkülähnliche Rechnung ohne ein calc() zu sehen:

body {
  /* Valid, but the math isn't actually performed just yet ... */
  --font-size: var(--base-font-size) * var(--modifier);

  /* ... so this isn't going to work */
  font-size: var(--font-size);
}

Der Trick ist, dass es funktioniert, solange du es schließlich in eine calc()-Funktion packst:

body {
  --base-font-size: 16px;
  --modifier: 2;
  --font-size: var(--base-font-size) * var(--modifier);

  /* The calc() is "deferred" down to here, which works */
  font-size: calc(var(--font-size));
}

Dies könnte nützlich sein, wenn du ziemlich viel mit deinen Variablen rechnest und der calc()-Wrapper im Code ablenkend oder unübersichtlich wird.

@property

Die @property „at-rule“ in CSS ermöglicht es dir, den Typ einer Custom Property sowie ihren Anfangswert und ob sie vererbt wird, zu deklarieren.

Es ist, als würdest du eine tatsächliche CSS-Eigenschaft erstellen und hättest die Möglichkeit zu definieren, wie sie genannt wird, ihre Syntax, wie sie mit der Kaskade interagiert, und ihren Anfangswert.

@property --x {
  syntax: '<number>';
  inherits: false;
  initial-value: 42;
}
Gültige Typen:
  • length (Länge)
  • number
  • percentage (Prozentwert)
  • length-percentage
  • color
  • image (Bild)
  • url
  • integer
  • angle (Winkel)
  • time (Zeit)
  • resolution
  • transform-list
  • transform-function
  • custom-ident (eine benutzerdefinierte Bezeichnerzeichenfolge)

Das bedeutet, dass der Browser weiß, mit welcher Art von Wert er es zu tun hat, anstatt anzunehmen, dass alles eine Zeichenfolge ist. Das bedeutet, du kannst Dinge auf eine Weise animieren, wie du es sonst nicht könntest.

Angenommen, du hast ein sternförmiges Symbol, das du mit @keyframes drehen möchtest und mit einem transform rotieren möchtest. Also machst du das:

.star {
  --r: 0deg;
  transform: rotate(var(--r));
  animation: spin 1s linear infinite;
}

@keyframes spin {
  100% {
    --r: 360deg;
  }
}

Das funktioniert tatsächlich nicht, da der Browser nicht weiß, dass 0deg und 360deg gültige Winkelwerte sind. Du musst sie als Typ <angle> mit @property definieren, damit das funktioniert.

@property --r {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

.star {
  --r: 0deg;
  transform: rotate(var(--r));
  animation: spin 1s linear infinite;
}

@keyframes spin {
  100% {
    --r: 360deg;
  }
}
Demo

Kommas in Werten

Das kann ein bisschen verwirrend sein. Vielleicht nicht so sehr das hier:

html {
  --list: 1, 2, 3;
}

Aber unten brauchst du ein scharfes Auge, um zu erkennen, dass der Fallback-Wert tatsächlich 1.2, 2 ist. Das erste Komma trennt den Fallback, aber der Rest ist Teil des Werts.

html {
  transform: scale(var(--scale, 1.2, 2));
}

Erfahre mehr über Fallbacks oben ⮑

Fortgeschrittene Verwendung

The Raven ist eine Technik, die Container-Queries mithilfe von Mathematik und Custom Properties emuliert. Sei vorbereitet, das steigert die Komplexität sofort von 0 auf 100!

Demo

Ändere die Größe dieser Demo, um zu sehen, wie ein Raster von Inline-Block-Elementen die Spaltenanzahl von 4 auf 3 auf 1 ändert.

Hier sind noch ein paar weitere Lieblingsbeispiele, die die fortgeschrittene Verwendung von Custom Properties zeigen:

Der initial- und Leerzeichen-Trick

Denke an @media-Queries und wie du, wenn eine Sache sich ändert (z. B. die Breite der Seite), mehrere Dinge steuern kannst. Das ist ungefähr die Idee bei diesem Trick. Du änderst eine Custom Property und steuerst mehrere Dinge.

Der Trick ist, dass der Wert initial für eine Custom Property einen Fallback auslöst, während ein leerer Leerzeichenwert dies nicht tut. Zur Erklärung definieren wir zwei global gültige Custom Properties, ON und OFF:

:root {
  --ON: initial;
  --OFF: ;
}

Angenommen, wir haben eine „dark“-Variationsklasse, die eine Reihe verschiedener Eigenschaften festlegt. Der Standard ist --OFF, kann aber jederzeit auf --ON umgeschaltet werden:

.module {
  --dark: var(--OFF);
}

.dark { /* could be a media query or whatever */
  --dark: var(--ON);
}

Jetzt kannst du --dark verwenden, um bedingt Werte festzulegen, die nur angewendet werden, wenn du --dark auf --ON umgestellt hast. Demo

Lea Verou hat einen großartigen Artikel, der all dies abdeckt.

Inline-Styles

Es ist absolut zulässig, eine Custom Property in HTML mit einem Inline-Style festzulegen.

<div style="--color: red;"></div>

Das hat, wie jeder Inline-Style, eine sehr hohe Spezifität.

Dies kann super nützlich sein, wenn das HTML Zugriff auf nützliche Styling-Informationen hat, die zu seltsam/schwierig wären, um sie in eine statische CSS-Datei zu packen. Ein gutes Beispiel dafür ist die Beibehaltung des Seitenverhältnisses eines Elements:

<div style="--aspect-ratio: 16 / 9;"></div>

Jetzt kann ich CSS einrichten, um eine Box genau dieser Größe zu erstellen, wo immer ich sie brauche. Der vollständige Artikel dazu ist hier, aber hier ist CSS, das Tricks wie die „ol’ padded box“ auf ein Pseudoelement anwendet, das die Box auf die gewünschte Größe drückt:

[style*="--aspect-ratio"] > :first-child {
  width: 100%;
}
[style*="--aspect-ratio"] > img {  
  height: auto;
} 
@supports (--custom: property) {
  [style*="--aspect-ratio"] {
    position: relative;
  }
  [style*="--aspect-ratio"]::before {
    content: "";
    display: block;
    padding-bottom: calc(100% / (var(--aspect-ratio)));
  }  
  [style*="--aspect-ratio"] > :first-child {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
  }  
}

Aber hey, heutzutage haben wir eine native aspect-ratio-Eigenschaft in CSS, daher könnte es in Zukunft sinnvoller sein, diese im Inline-Style festzulegen.

<div style="aspect-ratio: 16 / 9;"></div>

Hover und Pseudoelemente

Es gibt keine Möglichkeit, einen :hover-Style (oder andere Pseudoklassen/-elemente) mit Inline-Styles anzuwenden. Das heißt, es sei denn, wir tricksen mit Custom Properties. Angenommen, wir möchten benutzerdefinierte Hover-Farben für einige Boxen — wir können diese Informationen als Custom Property übergeben:

<div style="--hover-color: red;"><div>
<div style="--hover-color: blue;"><div>
<div style="--hover-color: yellow;"><div>

Und dann in CSS verwenden, das natürlich den Hover-Zustand eines Links stylen kann:

div:hover {
  background-color: var(--hover-color);
}

/* And use in other pseudos! */
div:hover::after {
  content: "I am " attr(style);
  border-color: var(--hover-color);
}

Custom Properties und JavaScript

JavaScript kann den Wert einer Custom Property festlegen.

element.style.setProperty('--x', value);

Hier ist ein Beispiel für ein rotes Quadrat, das mit Custom Properties positioniert ist, und JavaScript aktualisiert diese Custom Property-Werte mit der Mausposition:

Normalerweise denkt man, dass JavaScript Werte an CSS übergibt, was wahrscheinlich 99 % der Nutzung ausmacht, aber beachte, dass du auch Dinge von CSS an JavaScript übergeben kannst. Wie wir gesehen haben, kann der Wert einer Custom Property ziemlich permissiv sein. Das bedeutet, du könntest ihm eine logische Anweisung übergeben. Zum Beispiel:

html {
  --logic: if (x > 5) document.body.style.background = "blue";
}

Dann nimm diesen Wert und führe ihn in JavaScript aus:

const x = 10;

const logic = getComputedStyle(document.documentElement).getPropertyValue(
  "--logic"
);

eval(logic);

Custom Properties unterscheiden sich von Präprozessor-Variablen

Angenommen, du verwendest bereits Sass, Less oder Stylus. All diese CSS-Präprozessoren bieten Variablen, und das ist einer der Hauptgründe, sie als Teil deines Build-Prozesses zu haben.

// Variable usage in Sass (SCSS)
$brandColor: red;

.marketing {
  color: $brandColor;
}

Also, musst du dich dann überhaupt mit nativen CSS Custom Properties herumschlagen? Ja, solltest du. Hier ist der Grund in Kürze:

  • Native CSS Custom Properties sind mächtiger als Präprozessor-Variablen. Ihre Integration in die Kaskade im DOM ist etwas, das Präprozessor-Variablen niemals tun können.
  • Native CSS Custom Properties sind dynamisch. Wenn sie sich ändern (vielleicht über JavaScript oder mit einer Media Query), rendert der Browser neu, was er muss. Präprozessor-Variablen lösen sich beim Kompilieren in einen Wert auf und bleiben bei diesem Wert.
  • Eine native Funktion zu verwenden, ist gut für die Langlebigkeit deines Codes. Du musst natives CSS nicht präprozessieren.

Ich behandle dies viel detaillierter in dem Artikel „Was ist der Unterschied zwischen CSS-Variablen und Präprozessor-Variablen?“

Um ganz ehrlich zu sein, gibt es kleine Dinge, die Präprozessor-Variablen tun können, die mit Custom Properties allein schwer oder unmöglich sind. Angenommen, du möchtest die Einheiten von einem Wert entfernen. Das kannst du in Sass tun, aber du wirst es mit Custom Properties in CSS allein viel schwerer haben.

Kann man Custom Properties präprozessieren?

Irgendwie. Du kannst dies tun, mit Sass, um nur einen beliebten Präprozessor zu nennen:

$brandColor: red;
body {
--brandColor: #{$brandColor};
}

Das tut nichts anderes, als eine Sass-Variable in eine Custom Property zu verschieben. Das könnte manchmal nützlich sein, aber nicht besonders. Sass wird dort einfach --brandColor: red; erstellen, nicht die Custom Property verarbeiten.

Wenn ein Browser Custom Properties nicht unterstützt, dann ist das so. Du kannst einen Browser nicht zwingen, das zu tun, was Custom Properties tun, allein durch CSS-Syntax-Transformationen. Es gäbe vielleicht ein JavaScript-Polyfill, das dein CSS parst und es nachbildet, aber ich schlage das wirklich nicht vor.

Das PostCSS Custom Properties-Plugin führt jedoch CSS-Syntax-Transformationen durch, um zu helfen. Was es tut, ist, den Wert nach bestem Wissen und Gewissen herauszufinden und diesen zusammen mit der Custom Property auszugeben. Zum Beispiel:

:root {
  --brandColor: red;
}
body {
  color: var(--brandColor);
}

Wird so ausgegeben:

:root {
  --brandColor: red;
}
body {
  color: red;
  color: var(--brandColor);
}

Das bedeutet, dass du einen Wert erhältst, der in Browsern, denen die Unterstützung für Custom Properties fehlt, hoffentlich nicht fehlerhaft aussieht, aber keine der schicken Dinge unterstützt, die du mit Custom Properties machen kannst, und nicht einmal versucht, sie zu implementieren. Ich bin etwas skeptisch, wie nützlich das ist, aber ich denke, das ist ungefähr das Beste, was du tun kannst, und ich mag den Geist, zu versuchen, Dinge in älteren Browsern und neueren Browsern nicht kaputt zu machen.

Verfügbarkeit

Eine weitere Sache, die es wert ist, über den Unterschied zu sprechen, ist, dass bei einem CSS-Präprozessor die Variablen nur während des Verarbeitungsprozesses verfügbar sind. So etwas wie $brandColor ist in deinem HTML oder JavaScript bedeutungslos. Wenn du jedoch Custom Properties verwendest, kannst du Inline-Styles setzen, die diese Custom Properties verwenden, und sie werden funktionieren. Oder du kannst JavaScript verwenden, um ihre aktuellen Werte (im Kontext) herauszufinden, falls erforderlich.

Abgesehen von einigen etwas esoterischen Funktionen von Präprozessor-Variablen (z. B. einige mathematische Möglichkeiten) sind Custom Properties leistungsfähiger und nützlicher.

Custom Properties und Web Components (Shadow DOM)

Eine der gängigsten und praktischsten Möglichkeiten, Web Components zu stylen (z. B. eine <custom-component> mit Shadow DOM), ist die Verwendung von Custom Properties als Styling-Hooks.

Der Hauptzweck des Shadow DOM ist es, dass Stile nicht hinein oder heraus „durchsickern“, was eine Stil-Isolation bietet, die nichts anderes bietet, außer einem <iframe>. Stile kaskadieren jedoch immer noch hinein, ich kann nur nicht hineinselektieren. Das bedeutet, dass Custom Properties direkt hineingleiten werden.

Hier ist ein Beispiel:

Ein weiteres häufiges Vorkommen des Shadow DOM ist bei SVG und dem <use>-Element.

Video: „CSS Custom Properties dringen in das Shadow DOM ein“

Browser-Unterstützung

Diese Daten zur Browserunterstützung stammen von Caniuse, wo es mehr Details gibt. Eine Zahl gibt an, dass der Browser die Funktion ab dieser Version unterstützt.

Desktop

ChromeFirefoxIEEdgeSafari
4931Nein1610

Mobil / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712710.0-10.2

Du kannst für tiefere Browser-Unterstützung präprozessieren, mit starken Einschränkungen.

@supports

Wenn du bedingtes CSS schreiben möchtest, wenn ein Browser Custom Properties unterstützt oder nicht:

@supports (--custom: property) {
  /* Isolated CSS for browsers that DOES support custom properties, assuming it DOES support @supports */
}

@supports not (--custom: property) {
  /* Isolated CSS for browsers that DON'T support custom properties, assuming it DOES support @supports */
}

Danksagung

Vielen Dank an Miriam Suzanne für die Co-Autorschaft dieses Artikels!