CSS-Präprozessoren sind heutzutage aus kaum einem Projekt mehr wegzudenken. Von Sass über Less bis hin zu Stylus oder PostCSS – sie alle bieten eine enorme Menge an zusätzlichen Features, welche uns in CSS nicht zur Verfügung stehen. 

Doch entspricht das im Jahr 2024 noch der Realität, oder gibt es für viele der Killer-Features, die Präprozessoren mitbringen, mittlerweile vielleicht doch native Alternativen?

Wieso eigentlich den Sinn von Präprozessoren hinterfragen?

Die Einführung von CSS3 liegt mittlerweile über 10 Jahre zurück, das erste Release von Less bald 15 Jahre und Sass sogar 18 Jahre. Über diesen Zeitraum hat sich der CSS-Standard stark verändert, und gerade in den letzten Jahren wurden viele lang erwartete Funktionen in allen Evergreen-Browsern implementiert. Damit haben Entwickler heutzutage mehr Funktionen und Tools denn je zur Hand, um ihre Probleme zu lösen.

Viele der Killer-Features, für die in der Vergangenheit Präprozessoren in Projekten genutzt wurden, sind mittlerweile Teil des CSS-Standards und somit ohne Probleme direkt in CSS einsetzbar. Variablen und Nesting sind nur zwei der vielen Neuerungen, die CSS auch ohne Präprozessoren zu einer deutlich mächtigeren Stylesheet-Sprache machen, als sie es noch vor wenigen Jahren war.

Jedoch können nicht alle Features von Präprozessoren einfach durch native Lösungen ersetzt werden. Betrachten wir daher zunächst die wichtigsten Funktionen, die von CSS nativ bereitgestellt werden, und evaluieren, in welchen Fällen Präprozessoren noch benötigt werden oder ob es eventuell sogar möglich ist, komplett auf sie zu verzichten.

Der Funktionsumfang von nativem CSS

Wichtig: Einige der nachfolgenden Funktionen sind bereits seit Jahren in den meisten Browsern verfügbar, andere jedoch befinden sich noch in aktiver Entwicklung und sollten deshalb nur mit Vorsicht produktiv eingesetzt werden.

Für Vergleiche zwischen CSS und Präprozessor-Lösungen wird Sass/SCSS als Präprozessor-Lösung verwendet, da es laut aktuellen Umfragen den größten Marktanteil besitzt.

Variablen

Gerade in den anfänglichen Jahren von Präprozessoren waren Variablen das Killer-Feature überhaupt. Sei es zum Speichern von Farben für ein Theme oder Schriftgrößen und Schriftarten, um ein einheitliches User Interface zu gewährleisten.

Mit CSS Custom Properties existiert nun bereits seit einigen Jahren eine ebenbürtige native Lösung, um Variablen in CSS zu verwenden, ganz ohne Präprozessor. Schauen wir uns zunächst die beiden Implementierungen einmal an:

Auch wenn es kleine Unterschiede bei der Syntax gibt, sind die beiden Beispiele dennoch recht selbsterklärend. Allerdings ist es gerade bei Präprozessoren wichtig, nicht die Implementierung, sondern den Output zu anzuschauen, da letztendlich dieser im Browser landet.

Der Output für den obigen SCSS Code-Block sieht wie folgt aus:

Vergleicht man diesen Output nun mit der CSS-Implementierung, fällt der wesentliche Unterschied auf. Die SCSS-Variablen verschwinden nach dem Kompilieren und werden durch den Wert ersetzt, den die Variable zuvor hatte. Ganz anders bei Custom Properties. Sie bleiben Variablen und bringen so zusätzliche Flexibilität in den Browser. Folgende Funktionen können beispielsweise so vereinfacht umgesetzt werden:

  • Dark- / Light-Mode Switch
  • Responsive Schriftgrößen oder Abstände
  • User Theming Optionen

Zuvor mussten für solche Funktionalitäten meist mehrere CSS-Dateien gebaut werden, da diese nicht je nach Bedarf clientseitig angepasst werden konnten. So können Custom Properties in diesen Fällen zusätzlich die Komplexität des Build-Prozesses, sowie die Menge des Ausgabe-Codes verringern.

Des Weiteren können Custom Properties über Media Queries und Container Queries sowie durch JavaScript gesetzt und manipuliert werden und bieten damit eine ganze Reihe an Vorteilen gegenüber Präprozessor-Variablen.

Es gibt allerdings noch einen weiteren Unterschied, der oft übersehen wird. Während Custom Properties den normalen Regeln der Reihenfolge in CSS folgen und somit spätere Definitionen im selben Kontext den Wert überschreiben, können SCSS-Variablen mehrere Werte im selben Kontext annehmen. Dieses Verhalten lässt sich am besten anhand des folgenden Beispiels veranschaulichen:

Dieses Verhalten kann, je nach Anwendungsfall, sowohl Vorteile als auch Nachteile mit sich bringen. Es ist an dieser Stelle nur wichtig zu wissen, dass dieser Unterschied existiert.

Media- und Container-Queries

Queries stellen im Zusammenhang mit Variablen einen Sonderfall dar. Während man erwarten würde, dass var() ohne Einschränkungen innerhalb einer Media- oder Container-Query verwenden kann, ist dies leider nicht der Fall. Obwohl es mit @custom-media bereits eine fertig spezifizierte Lösung gibt, ist diese momentan leider noch in keinem Browser implementiert.

Für Projekte, in denen dies ein essentielles Feature ist, bietet PostCSS mit dem Custom Media Plugin die beste Lösung, um bereits jetzt die neue Syntax für Query-Variablen zu verwenden.

Typing

Ein weiteres wichtiges Merkmal von Präprozessoren sind Typen. SCSS unterstützt beispielsweise die folgenden:

  • Number
  • String
  • Color
  • Listen
  • Maps
  • Boolean
  • null

Während null im Kontext von CSS nur wenig Sinn macht, und wir uns Listen und Maps in einem späteren Abschnitt ansehen werden, können die restlichen Typen in ähnlicher Form bereits in CSS verwendet werden.

Mit der @property-Schreibweise wurde eine Möglichkeit eingeführt, Custom Properties in verschiedener Hinsicht zu konfigurieren und damit auch eine Syntax zu definieren, die ähnlich wie Typen funktioniert. Schauen wir uns eine solche Konfiguration an:

Wir sehen hier gleich mehrere Funktionen, welche diese neue Definition mit sich bringt:

  1. syntax: Welcher Syntax müssen Werte folgen? 
  2. inherits: Werden gesetzte Werte vererbt?
  3. initial-value: Initialer Wert beim Anlegen der Custom Property

Während initial-value recht selbsterklärend ist, sind die anderen beiden Eigenschaften etwas komplexer, bieten jedoch ein enormes Potenzial. Für die Syntax können zum Beispiel neben Werten wie <number> und <color> auch Listen von erlaubten Werten übergeben werden. Ähnlich wie bei Union Types in TypeScript kann so zum Beispiel folgendes definiert werden:

Die Syntax wird zur Validierung der Korrektheit bei der Zuweisung neuer Werte verwendet. Alle Zuweisungen außerhalb des erlaubten Wertebereichs werden dabei ignoriert. Das folgende Beispiel verdeutlicht dieses Verhalten:

Dadurch, dass yellow nicht Teil der erlaubten Werte ist, wird die Zuweisung verworfen und auf den initial-value zurückgegriffen. Auf diese Weise kann beispielsweise auch der Boolean-Typ aus SCSS, der in CSS nicht existiert, einfach mit „true | false“ nachgebildet werden. Eine ausführliche Liste erlaubter Syntax-Werten ist hier zu finden.

Der Parameter inherits bestimmt, ob Zuweisungen der Variable in der Kaskade weitergegeben werden. Elemente, welche eine Custom Property mit dem Wert „inherits: false“ überschreiben, geben diesen neu gesetzten Wert somit nicht an ihre Kindelemente weiter. Diese greifen stattdessen auf den initial-value zurück. Dieses Verhalten unterscheidet sich deutlich von herkömmlichen Custom Properties, deren Werte der Kaskade folgen.

Nesting

Wie Variablen war auch das Verschachteln von Selektoren, Kombinatoren und Media Queries – das sogenannte Nesting – lange Zeit ein Feature, das viele dazu veranlasste, Präprozessoren zu verwenden. Durch Nesting können unter anderem unnötige Duplikationen bei der Definition von Selektoren vermieden und die Übersichtlichkeit gewahrt werden. Nachfolgend ein typisches Beispiel für Nesting: 

Dies ist allerdings kein reines Präprozessor-Feature mehr. Das vorherige Beispiel ist nicht nur SCSS, sondern auch valides CSS, das von Browsern ohne zusätzlichen Buildschritt interpretiert werden kann. 

Auch wenn die Syntax identisch ist, gibt es jedoch fundamentale Unterschiede, die erwähnt werden sollten:

1. Wenn Tags genested werden, ist, anders als bei anderen Selektoren, beim CSS-Nesting, ein & vor dem Tag-Namen erforderlich.

2. Im Unterschied zu SCSS werden in CSS Selektor-Listen von Elementen mit verschachtelten Selektoren aus Performance-Gründen mittels :is() aufgelöst. Dadurch kann es bei komplexen Verschachtelungen in CSS zu einer anderen Spezifität als im Präprozessor-Pendant kommen.

3. Die Möglichkeit, Selektoren aus mehreren Teilstrings, à la BEM, mit Hilfe von CSS-Nesting zusammenzusetzen, existiert nicht. Projekte, die auf Klassennamenkonventionen wie BEM zurückgreifen, sollten daher in Erwägung ziehen, ob eine Präprozessor-Lösung möglicherweise die bessere Lösung darstellt. An dieser Stelle ist jedoch das neue CSS-Feature @scope erwähnenswert, das Klassennamenkonventionen wie BEM in Zukunft komplett überflüssig machen könnte.

Obwohl Nesting keine neuen Entwicklungs­möglichkeiten eröffnet, ist es dennoch eine wichtige und erwähnenswerte Erweiterung des CSS-Standards, die in allen Punkten mit den etablierten Präprozessor-Lösungen mithalten kann.

Media Query Range Syntax

Obwohl es lediglich Syntactic Sugar ist, finde ich, dass die neue Media Query Range Syntax einen Platz in diesem Artikel verdient hat. 

Dadurch können komplexe Media- und auch Container-Queries deutlich lesbarer und kürzer geschrieben werden als noch vor einigen Jahren. Mit Safari hat im letzten Jahr auch der letzte der Evergreen-Browser dieses Feature implementiert, so dass einem Einsatz in Projekten nichts mehr im Wege steht. 

Wichtig ist an dieser Stelle jedoch zu erwähnen, dass in den Fällen, in denen der Browser die neue Syntax nicht unterstützt, der gesamte Styleblock ignoriert wird. Insbesondere in Projekten mit hohen Anforderungen in Sachen Rückwärtskompatibilität sollte das Feature daher nur mit Vorsicht oder einer entsprechenden Präprozessor-Lösung verwendet werden. 

Funktionen

Ein weiterer großer Vorteil von Präprozessoren ist die große Anzahl an Funktionen. Zwei der wichtigsten Anwendungsgebiete für diese sind Berechnungen und Farbmodifikationen. Während einfache mathematische Berechnungen mit Hilfe von calc() schon lange in CSS möglich sind, waren komplexe Berechnungen oder das Ändern von Farben lange Zeit ein Problem. Doch auch hier hat sich in den letzten Jahren viel getan, schauen wir uns deshalb die neuesten Funktionen im Bereich CSS einmal genauer an.

Mathematische Funktionen

Im Bereich der mathematischen Funktionen gab es besonders viele Neuerungen, deshalb nachfolgend eine Liste aller in CSS verfügbaren Funktionen, die im mathematischen Kontext verwendet werden können.

Funktion Beschreibung
calc() Die Basisfunktion zur Berechnung von Werten in CSS
min(value, value, …) Gibt den kleinsten aus einer Menge an Werten zurück
max(value, value, …) Gibt den größten aus einer Menge an Werten zurück
clamp(min, value, max) Begrenzt einen Wert auf einen bestimmten Bereich
round(strategy, value, interval) Multifunktionelle Rundungsfunktion

Strategy bestimmt die Rundungsstrategie, folgende Werte sind dabei möglich:

  • up: Immer aufrunden (ceil)
  • down: Immer abrunden (ceil)
  • nearest (default): Normales runden
  • to-zero: Als integer (trunc)

Value ist der zu rundende Wert, welcher relativ zum Rundungsintervall (interval) gerundet wird (mehr erfahren). 

mod(value1, value2) Mathematische Modulo-Funktion
rem(value1, value2) Remainder-Funktion
sin(value), cos(value), tan(value), asinvalue), acos(value), atan(value), atan2(value1, value2) Trigonometrische Funktionen
pow(value1, value2) Potenziert value1 mit dem Exponenten value2
sqrt(value) Quadratwurzel
hypot(value, …) Gibt die Quadratwurzel aus der Summe der Quadrate ihrer Argumente zurück.
log(value1, value2?) Logarithmus der Zahl value1 zur Basis von value2
exp(value) Potenziert der e mit dem Exponenten value
abs(value) Gibt den absoluten Wert von value zurück
sign(value) Gibt -1 zurück, wenn value negativ ist und 1 falls value positiv ist

Neben Funktionen gibt es neuerdings auch diverse mathematische Konstanten, welche innerhalb von Berechnungen verwendet werden können:

e Eulersche Zahl
pi Kreiszahl / Pi / π
infinity / -infinity Unendlich / negativ Unendlich 
NaN Not a Number – Wert

Vergleicht man diese Menge an mathematischen Funktionen mit denen von Präprozessoren, so gibt es nur noch wenige Unterschiede. Lediglich kleinere Funktionen wie random() existieren derzeit noch nicht in nativem CSS.

Farbfunktionen

Ähnlich wie im Bereich der mathematischen Funktionen hat sich auch im Bereich der Farbfunktionen in den letzten Jahren einiges getan. So können Farben heute nicht nur in den bekannten Formaten Hexadezimal, RGB und HSL angegeben werden, sondern auch in einer Vielzahl weiterer Formate, die verschiedene Vorteile mit sich bringen. Zu diesen Formaten gehören unter anderem:

  • hwb()
  • lab()
  • lch()
  • oklab()
  • oklch()

Wer mehr über die verschiedenen Formate und die entsprechenden Farbräume erfahren möchte, dem kann ich diesen Artikel von Evil Martians ans Herz legen.

Seit Mitte 2023 steht uns zudem die Funktion color-mix() zur Verfügung, mit der Farben gemischt und das Ergebnis in einem vordefinierten Farbraum ausgegeben werden kann. Konkret kann das so aussehen:

Zudem wird momentan an der Integration der sogenannten Relative Color Syntax gearbeitet. Diese ermöglicht die Erstellung von neuen Farben relativ zu einer Ursprungsfarbe und erleichtert damit die Abbildung von Präprozessor-Funktionen wie darken, lighten, transparentize oder saturate sowie vieler weiterer Farbfunktionen in CSS. 

Wichtig ist dabei, die Syntax zu verstehen, da diese auf den ersten Blick etwas verwirrend sein kann. Folgendes Schaubild zeigt die Einzelteile einer solchen Definition auf und beschreibt diese.

Abbildung: Sass, Less & Co.: Relative Color Syntax

Relative Color Syntax

Nachfolgend ein Beispiel, wie mit Hilfe dieser Funktion Farben in verschiedenster Weise manipuliert werden können.

Wie ersichtlich wird, ermöglicht die Relative Color Syntax eine Vielzahl von Farbmanipulationen. Die oben gezeigten Beispiele geben nur einen kleinen Einblick in das, was tatsächlich möglich ist. Die neue Syntax kann zum Beispiel bei der Erstellung einer ganzen Farbpalette mit nur einer Ausgangsfarbe helfen und den Wartungsaufwand bei komplexen Anwendungen mit mehreren Themes verringern.

If-Statements

Obwohl das Hauptfeature, welches ermöglicht, If-ähnliche Conditions in CSS zu schreiben, noch in der Entwicklung ist, ist es wichtig, aufzuzeigen, welche Features uns in naher Zukunft zur Verfügung stehen.

Ich nenne es bewusst If-ähnlich, da es mit bekannten if/else Bedingungen wenig zu tun hat, allerdings selbiges ermöglicht. 

Bei dem Feature handelt es sich um die sogenannten Style-Queries. Es ist ein Subfeature von Container-Queries, mit deren Hilfe ein Set von Styles abhängig von Variablenwerten oder anderen Styles gesetzt werden kann. Nachfolgend ein Beispiel:

Wie aus dem Beispiel ersichtlich wird, verwenden Style Queries eine ähnliche Syntax wie Container Queries. Neu ist einzig und allein die style()-Funktion, welche dem Browser mitteilt, dass er nachfolgende Parameter als Styles zu interpretieren und zu evaluieren hat. 

Diese neue Syntax ermöglicht es, einen großen Teil der Styling-Logik, die in den letzten Jahren oft in JavaScript ausgelagert wurde, zurück ins CSS zu holen. So müssen zukünftig keine Klassen an diversen Stellen mehr getoggelt oder Styling Properties an Komponenten durchgereicht werden. 

All dies ist zukünftig mit Hilfe von Custom-Properties nativ möglich. Gerade in Verbindung mit der im Abschnitt Variablen erwähnten @property-Syntax kriegen Entwickler damit ein unglaublich mächtiges neues Tool an die Hand.

Wer mehr über Container- und Style-Queries erfahren möchte, kann sich gerne den Beitrag CSS Container Queries – das unabwendbare Ende von “Mobile First” anschauen.

Wofür brauchen wir denn noch Präprozessoren?

Nach einem Blick auf die vielen neuen Funktionen im Bereich CSS stellt sich berechtigterweise die Frage: Brauchen wir Präprozessoren überhaupt noch? Denn tatsächlich gibt es einen Großteil der Funktionen, die in der Vergangenheit exklusiv in Präprozessoren zur Verfügung standen, mittlerweile nativ in CSS, wie wir bis hierhin gesehen haben.

Es gibt jedoch einige Funktionen, die derzeit nicht durch CSS ersetzt werden können. Im Folgenden betrachten wir die wohl wichtigsten davon.

Selector Compounding (BEM)

Wie bereits im Abschnitt Nesting erwähnt, verfügt die aktuelle Version von nativem Nesting über keine Möglichkeit, Selektoren aus mehreren einzelnen Strings zusammenzufügen. Aus verschiedenen Gründen, unter anderem der Komplexität der Implementierung, gibt es derzeit zudem keine Ambitionen, dies nachträglich zu ergänzen.

Stylesheet Concatenation

Es ist nicht möglich, ohne Buildschritt mehrere CSS-Dateien zusammenzufügen. Die alternative Lösung mittels nativem @import wird auf Grund des Performance Impacts grundsätzlich als Bad-Practice angesehen.

Maps, Lists und Iteratoren

Datentypen wie Maps, Listen oder Arrays und die dazugehörigen Hilfsfunktionen finden sich ebenfalls nicht im CSS-Standard wieder.

Mixins

In den letzten Jahren hat sich der Einsatz von Mixins verringert, und es wird immer populärer, Styles in Form von Utility-Klassen bereitzustellen, um sie zwischen Komponenten zu teilen. Obwohl der Ansatz verschieden ist, erzielt er das gleiche Ergebnis. Wer dennoch auf Mixins zurückgreifen möchte, benötigt weiterhin einen geeigneten Präprozessor.

Funktionen

Auch wenn es nicht möglich ist, Funktionen à la Sass & Co. in CSS zu schreiben, kann ich mir dennoch vorstellen, dass einige der Neuerungen der letzten Jahre viel der Logik, die zuvor durch etwaige Funktionen abgefangen wurde, ersetzen können. So hängt es auch hier von den individuellen Anforderungen des Projekts ab, ob und inwiefern Präprozessoren zum Einsatz kommen müssen. 

Sass, Less & Co – Fazit

Viele Projekte brauchen heutzutage keine Präprozessoren mehr, da ein Großteil der Alleinstellungsmerkmale, die Sass, Less & Co. zu ihrer großen Beliebtheit verholfen haben, längst Teil des CSS-Standards sind und dabei teilweise sogar deutlich mehr Funktionen mit sich bringen als ihre Präprozessoren-Pendants. Dennoch haben sie weiterhin ihre Daseinsberechtigung, so sind viele der Funktionen, die in diesem Artikel präsentiert wurden, erst seit kurzem Teil des CSS-Standards und werden dementsprechend von nicht allen Geräten vollständig unterstützt. Gleichzeitig gibt es nur wenige Argumente, die dafür sprechen würden, eine komplett fertige und funktionierende Codebase vollständig auf CSS umzuziehen.

Gerade für neue Projekte können die frischen Features jedoch ein Anreiz sein, auf ein Setup ohne Präprozessor umzusteigen, um sich mit den neuen Funktionen vertraut zu machen. Denn falls erforderlich, können diese auch in Kombination mit Präprozessoren verwendet werden. Das ist einer der großen Vorteile von CSS: Neue Funktionalitäten sind in den meisten Fällen kompatibel mit Präprozessoren und können so unabhängig von Projekt und Setup verwendet werden.

Titelmotiv: Photo by Mika Baumeister on Unsplash

Quentin Albert

Große Auswahl an günstigen Domain-Endungen – schon ab 0,08 € /Monat
Jetzt Domain-Check starten