CSS-Variablen erlauben komfortabel, Farben und andere CSS-Eigenschaften in CSS-Stylesheets zu definieren – wenn da nur nicht ältere Browser wären, die CSS-Variablen nicht unterstützen, und damit den schönen CSS-Plan zerstören.

Aber keine Bange: SASS und CSS-Variablen sind die perfekte Kombination, um eine bombensichere Unterstützung für CSS-Variablen zu erreichen.

Wofür brauche ich eigentlich CSS-Variablen?

CSS-Variablen bzw. CSS Custom Properties erlauben eine übersichtliche, wiederverwendbare Definition von CSS-Eigenschaften. Wie schon von SASS, LESS und anderen CSS-Präprozessoren bekannt, kann eine solche Variable mehrfach verwendet werden – eine Änderung an der zentralen Definition ändert zeitgleich und zuverlässig die CSS-Ausgabe:

:root {
  --color-background: #ffffee;
  --color-text: #111100;
}

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

article {
  border: 1px dotted var(--color-text);
  padding: 1em;
}

Das ist besonders praktisch, wenn man einem Styleguide folgt, später übersichtlich Werte im CSS austauschen möchte, oder einen zentralen Ansatzpunkt für CSS-Änderung zum Beispiel durch Redakteure haben möchte.

Dream Team: Media Queries und CSS-Variablen

Gleichzeitig kann die Definition von CSS-Variablen auch in Abhängigkeit von Media Queries (@media) definiert werden. So kann sich der Inhalt einer Variable für die Druckausgabe oder z.B. für die Ausgabe im Dark Mode ändern.

@media screen {
  :root {
    --color-background: #ffffee;
    --color-text: #111100;
    --color-link: #000899;
    --color-link-hover: #1520c7;
  }
}
@media (prefers-color-scheme: dark) {
  :root {
    --color-background: #333;
    --color-text: #fff;
    --color-link: #0eb9e7;
    --color-link-hover: #39cbf6;
  }
}
@media print {
  :root {
    --color-link: blue;
    --color-link-hover: blue;
  }
}

Damit entfällt die Notwendigkeit, Variablen durch SASS oder LESS definieren zu müssen – und damit in bestimmten Projekten die Notwendigkeit, überhaupt SASS oder LESS zu verwenden…

Alte Browser, alte Leier

…wenn da nicht (gar nicht so) alte Browser wären, die CSS-Variablen nicht interpretieren können. So sind alle Versionen des Microsoft Internet Explorers, sowie ältere Versionen von Microsoft Edge und Apple Safari nicht in der Lage, CSS-Variablen zu verarbeiten.

Netterweise gibt es aber einen Weg, auch diesen Browsern grundsätzliche Wünsche mitzuteilen:

body {
  color: #111100;
  color: var(--color-text);
  background-color: #ffffee;
  background-color: var(--color-background);
}

CSS ist so aufgebaut, dass bei einer mehrfachen Zuweisung von CSS-Eigenschaften die letzte Eigenschaft verwendet wird, die erfolgreich vom Browser interpretiert werden konnte.

In unserem obigen Beispiel werden ältere Browser nicht in der Lage sein, eine korrekte Ersetzung für Print und Dark Mode durchzuführen – aber wenigstens grundsätzlich sieht die Seite gut aus. Im Sinne von progressive enhancement (oder graceful degradation) hat man mit dieser Lösung schon einen passablen Job gemacht.

Dieses Arbeiten mit Fallbacks hat aber zwei Nachteile:

  • Bei jeder Verwendung einer CSS-Variable muss ein Fallback definiert werden.
  • Der Wert des Fallbacks sollte grundsätzlich mit einem der Werte der CSS-Variable übereinstimmen (in der Regel dem Wert, den die Variable für Desktop-Browser auf dem Bildschirm annimmt).

Also doch SASS!

Netterweise bieten sich SASS-Variablen an, um CSS-Variablen zu definieren. Unser erster Schritt: Wir definieren die Werte, die wir später als CSS-Variablen verwenden wollen. Dazu benutzen wir aber nicht reguläre SASS-Variablen, sondern stattdessen SASS-Maps. Diese praktischen Konstrukte erlauben die Definition einer Liste von Schlüssel-Wert-Paaren:

$cssVariablesScreen: (
  'color-background': #ffffee,
  'color-text': #111100,
  'color-link': #000899,
  'color-link-hover': #1520c7
);

…die in SASS nur wenig komplizierter als eine SASS-Variable ausgegeben werden können:

body {
  color: map-get($cssVariables, color-background);
}

Wir aber interessieren uns für die Ausgabe der SASS-Variablen als CSS-Variablen. Hier hilft uns die SASS-Map, die in einer Schleife (@each) alle Schlüssel-Wert-Kombinationen ausgeben kann:

:root {
  @each $name, $value in $cssVariablesScreen {
    --#{$name}: #{$value};
  }
}

Auf diese Weise können wir für jede Media Query eine SASS-Map erzeugen, und den dazu passenden Block an CSS-Variablen erzeugen lassen:

$cssVariablesDarkMode: (
  'color-background': #333,
  'color-text': #fff,
  'color-link': #0eb9e7,
  'color-link-hover': #39cbf6
);
$cssVariablesPrint: (
  'color-link': blue,
  'color-link-hover': blue
);

:root {
  @media screen {
    @each $name, $value in $cssVariablesScreen {
      --#{$name}: #{$value};
    }
  }
  @media (prefers-color-scheme: dark) {
    @each $name, $value in $cssVariablesDarkMode {
      --#{$name}: #{$value};
    }
  }
  @media print {
    @each $name, $value in $cssVariablesPrint {
      --#{$name}: #{$value};
    }
  }
}

SASS-Mixins für die Hebearbeit

Um uns unnötige Wiederholungen von SASS-Code zu vermeiden, verwenden wir SASS-Mixins (@mixin). So kann das Durchlaufen von SASS-Maps zum Erzeugen von CSS-Variablen wunderbar in ein solches Mixin ausgelagert werden:

@mixin make-css-variables($cssVariables) {
  @each $name, $value in $cssVariables {
    --#{$name}: #{$value};
  }
}

:root {
  @media screen {
    @include make-css-variables($cssVariablesScreen)
  }
  @media (prefers-color-scheme: dark) {
    @include make-css-variables($cssVariablesDarkMode)
  }
  @media print {
    @include make-css-variables($cssVariablesPrint)
  }
}

Damit haben wir zumindest schonmal die Ausgabe der CSS-Variablen – haben aber leider noch nichts gewonnen, was die Fallbacks für ältere Browser angeht.

Tatsächlich haben wir aber schon alle Teile herumliegen, die folgende Fallbacks möglich machen:

body {
  color: #111100;
  color: var(--color-text);
  background-color: #ffffee;
  background-color: var(--color-background);
}

Theoretisch könnten wir uns der Aufgabe in SASS wie folgt nähern:

body {
  color: map-get($cssVariables, color-text);
  color: var(--color-text);
  background-color: map-get($cssVariables, color-background);
  background-color: var(--color-background);
}

Bei einem genauen Blick erkennt man hier aber, das die beiden Zeilen direkt zusammenhängen, und ganz wunderbar durch das folgende Mixin abgebildet werden können:

@mixin variable-fallback($property, $name) {
  #{$property}: map-get($cssVariablesScreen, $name);
  #{$property}: var(--#{$name});
}

body {
  @include variable-fallback(color, color-text);
  @include variable-fallback(background-color, color-background);
}

Mit ein bisschen zusätzlicher Fehlerbehandlung und mehr Flexibilität, welche SASS-Map für den Fallback verwendet werden soll:

@mixin variable-fallback($property, $name, $cssVariables: $cssVariablesScreen) {
  @if map-has-key($cssVariables, $name) {
    #{$property}: map-get($cssVariables, $name);
  } @else {
    @warn "Missing CSS variable: #{$name}"
  }
  #{$property}: var(--#{$name});
}

Fazit

CSS-Variablen mit Kompatibilität für Browser, die CSS-Variablen nicht unterstützen, kann man mit wenig Schreibarbeit via SASS lösen. Die Schritt sind:

  1. CSS-Variablen als SASS-Map definieren.
  2. Die SASS-Map zum Erzeugen der CSS-Variablen verwenden – gegebenenfalls via Mixin.
  3. Bei jeder Verwendung einer CSS-Variable ein Mixin verwenden, das gleichzeitig die CSS-Variable und den Fallback-Wert im CSS ausgibt.
// Defining SASS variables to build CSS variables
// --------------------------------------------------------------

$cssVariablesScreen: (
  'color-background': #ffffee,
  'color-text': #111100,
  'color-link': #000899,
  'color-link-hover': #1520c7
);
$cssVariablesDarkMode: (
  'color-background': #333,
  'color-text': #fff,
  'color-link': #0eb9e7,
  'color-link-hover': #39cbf6
);
$cssVariablesPrint: (
  'color-link': blue,
  'color-link-hover': blue
);

// Defining mixins for converting SASS variables to CSS variables
// --------------------------------------------------------------

@mixin make-css-variables($cssVariables) {
  @each $name, $value in $cssVariables {
    --#{$name}: #{$value};
  }
}

@mixin variable-fallback($property, $name, $cssVariables: $cssVariablesScreen) {
  @if map-has-key($cssVariables, $name) {
    #{$property}: map-get($cssVariables, $name);
  } @else {
    @warn "Missing CSS variable: #{$name}"
  }
  #{$property}: var(--#{$name});
}

// Convert SASS variables to CSS variables
// --------------------------------------------------------------

:root {
  @media screen {
    @include make-css-variables($cssVariablesScreen)
  }
  @media (prefers-color-scheme: dark) {
    @include make-css-variables($cssVariablesDarkMode)
  }
  @media print {
    @include make-css-variables($cssVariablesPrint)
  }
}

// Use CSS variables
// --------------------------------------------------------------

body {
  @include variable-fallback(color, color-text);
  @include variable-fallback(background-color, color-background);
}