Die Vorteile der Verwendung benutzerdefinierter Properties in Designsystemen und Komponentenbibliotheken.
Ich bin David und Senior-Frontend-Entwickler bei Nordhealth. Ich arbeite am Design und der Entwicklung unseres Designsystems Nord, einschließlich der Erstellung von Webkomponenten für unsere Komponentenbibliothek. Ich möchte Ihnen zeigen, wie wir die Probleme beim Stylen von Webkomponenten mithilfe von benutzerdefinierten CSS-Eigenschaften gelöst haben. Außerdem möchte ich Ihnen einige der anderen Vorteile der Verwendung benutzerdefinierter Eigenschaften in Designsystemen und Komponentenbibliotheken erläutern.
So entwickeln wir Webkomponenten
Für die Erstellung unserer Webkomponenten verwenden wir Lit, eine Bibliothek mit viel Boilerplate-Code wie Status, stilisierte Bereiche, Vorlagen und mehr. Lit ist nicht nur schlanker, sondern basiert auch auf nativen JavaScript-APIs. Das bedeutet, dass wir ein schlankes Code-Bundle bereitstellen können, das die Funktionen des Browsers nutzt.
Das Beste an Web-Komponenten ist jedoch, dass sie mit fast jedem vorhandenen JavaScript-Framework oder sogar ohne Framework funktionieren. Sobald auf das Haupt-JavaScript-Paket auf der Seite verwiesen wird, ähnelt die Verwendung einer Webkomponente der Verwendung eines nativen HTML-Elements. Das einzige wirkliche Anzeichen dafür, dass es sich nicht um ein natives HTML-Element handelt, ist der durchgängige Bindestrich innerhalb der Tags. Dies ist ein Standard, der dem Browser signalisiert, dass es sich um eine Webkomponente handelt.
Shadow-DOM-Stil-Kapselung
Ähnlich wie native HTML-Elemente ein Shadow DOM haben, haben auch Webkomponenten ein solches. Das Shadow-DOM ist ein ausgeblendeter Knotenbaum innerhalb eines Elements. Am besten können Sie sich das vorstellen, wenn Sie den Web-Inspektor öffnen und die Option „Shadow DOM-Baum anzeigen“ aktivieren. Sehen Sie sich dann ein natives Eingabeelement im Inspector an. Sie können es jetzt öffnen und alle darin enthaltenen Elemente sehen. Sie können das sogar mit einer unserer Webkomponenten ausprobieren. Sehen Sie sich dazu unsere benutzerdefinierte Eingabekomponente an, um das Shadow DOM zu sehen.
Einer der Vorteile (oder Nachteile, je nach Sichtweise) von Shadow DOM ist die Stilkapselung. Wenn Sie CSS in Ihrer Webanwendung schreiben, können diese Stile nicht austreten und sich auf die Hauptseite oder andere Elemente auswirken. Sie sind vollständig in der Komponente enthalten. Außerdem kann CSS, das für die Hauptseite oder eine übergeordnete Webkomponente geschrieben wurde, nicht in Ihre Webkomponente eindringen.
Diese Kapselung von Stilen ist ein Vorteil unserer Komponentenbibliothek. So können wir besser dafür sorgen, dass eine Komponente, die jemand verwendet, unabhängig von den auf der übergeordneten Seite angewendeten Stilen wie vorgesehen aussieht. Zur Sicherheit fügen wir all: unset;
auch der Wurzel oder dem „Host“ aller Webkomponenten hinzu.
Was ist aber, wenn jemand, der Ihre Webanwendung verwendet, einen triftigen Grund hat, bestimmte Stile zu ändern? Vielleicht gibt es eine Textzeile, die aufgrund ihres Kontexts mehr Kontrast benötigt, oder ein Rahmen muss dicker sein? Wie können Sie diese Stiloptionen nutzen, wenn keine Stile in Ihre Komponente einfließen können?
Hier kommen benutzerdefinierte CSS-Properties ins Spiel.
Benutzerdefinierte CSS-Properties
Benutzerdefinierte Eigenschaften sind sehr treffend benannt: Sie sind CSS-Eigenschaften, die Sie selbst benennen und mit dem gewünschten Wert versehen können. Die einzige Voraussetzung ist, dass Sie ihnen zwei Bindezeichen voranstellen. Nachdem Sie die benutzerdefinierte Property deklariert haben, kann der Wert in Ihrem CSS mithilfe der Funktion var()
verwendet werden.
Bei der Übernahme werden alle benutzerdefinierten Properties übernommen. Das entspricht dem üblichen Verhalten regulärer CSS-Properties und -Werte. Jede benutzerdefinierte Property, die auf ein übergeordnetes Element oder das Element selbst angewendet wird, kann als Wert für andere Properties verwendet werden. Wir verwenden benutzerdefinierte Properties für unsere Design-Tokens und wenden sie über unser CSS-Framework auf das Stammelement an. Das bedeutet, dass alle Elemente auf der Seite diese Tokenwerte verwenden können, z. B. eine Webanwendung, eine CSS-Hilfsklasse oder ein Entwickler, der einen Wert aus unserer Liste der Tokens auswählen möchte.
Mithilfe der Funktion var()
können wir benutzerdefinierte Eigenschaften von Webkomponenten übernehmen und Entwicklern eine genauere Kontrolle beim Stylen unserer Komponenten ermöglichen.
Benutzerdefinierte Eigenschaften in einer Nord-Webkomponente
Wenn wir eine Komponente für unser Designsystem entwickeln, gehen wir beim CSS sehr sorgfältig vor. Wir streben einen schlanken, aber sehr wartungsfreundlichen Code an. Die Design-Tokens sind als benutzerdefinierte Properties in unserem Haupt-CSS-Framework im Stammelement definiert.
Auf diese Tokenwerte wird dann in unseren Komponenten verwiesen. In einigen Fällen wird der Wert direkt auf die CSS-Eigenschaft angewendet. In anderen Fällen wird eine neue kontextbezogene benutzerdefinierte Property definiert und der Wert darauf angewendet.
Außerdem abstrahieren wir einige Werte, die für die Komponente spezifisch sind, aber nicht in unseren Tokens enthalten sind, und wandeln sie in eine benutzerdefinierte Kontexteigenschaft um. Benutzerdefinierte Properties, die sich auf die Komponente beziehen, bieten zwei wichtige Vorteile. Erstens: Wir können unser CSS schlanker gestalten, da dieser Wert auf mehrere Properties innerhalb der Komponente angewendet werden kann.
Außerdem lassen sich Änderungen am Komponentenstatus und an der Variante ganz einfach vornehmen. Es muss nur die benutzerdefinierte Eigenschaft geändert werden, um alle diese Eigenschaften zu aktualisieren, wenn Sie beispielsweise einen Hover- oder aktiven Status oder in diesem Fall eine Variante stylen.
Der größte Vorteil besteht jedoch darin, dass wir durch die Definition dieser benutzerdefinierten Kontexteigenschaften für eine Komponente eine Art benutzerdefinierte CSS-API für jede unserer Komponenten erstellen, die vom Nutzer dieser Komponente genutzt werden kann.
Im vorherigen Beispiel ist eine unserer Webkomponenten mit einer benutzerdefinierten Kontexteigenschaft zu sehen, die über einen Auswahlmechanismus geändert wurde. Das Ergebnis dieses gesamten Ansatzes ist eine Komponente, die dem Nutzer ausreichend Flexibilität beim Styling bietet und gleichzeitig die meisten der tatsächlichen Stile im Auge behält. Als Bonus haben wir als Komponentenentwickler die Möglichkeit, diese vom Nutzer angewendeten Stile abzufangen. Wenn wir eine dieser Eigenschaften anpassen oder erweitern möchten, können wir das tun, ohne dass der Nutzer seinen Code ändern muss.
Wir finden diesen Ansatz äußerst leistungsstark, nicht nur für uns als Entwickler unserer Designsystemkomponenten, sondern auch für unser Entwicklungsteam, wenn es diese Komponenten in unseren Produkten verwendet.
Benutzerdefinierte Eigenschaften weiter optimieren
Zum Zeitpunkt der Erstellung dieses Artikels sind diese benutzerdefinierten Kontexteigenschaften nicht in unserer Dokumentation aufgeführt. Wir planen jedoch, sie dort aufzunehmen, damit unser gesamtes Entwicklungsteam diese Eigenschaften verstehen und nutzen kann. Unsere Komponenten sind bei npm mit einer Manifestdatei verpackt, die alle Informationen zu ihnen enthält. Die Manifestdatei wird dann als Daten verwendet, wenn unsere Dokumentationswebsite mit Eleventy und der Global Data-Funktion bereitgestellt wird. Wir planen, diese kontextbezogenen benutzerdefinierten Properties in diese Manifestdatendatei aufzunehmen.
Ein weiterer Bereich, den wir verbessern möchten, ist die Übernahme von Werten für diese benutzerdefinierten Kontexteigenschaften. Wenn Sie beispielsweise die Farbe von zwei Trennlinien anpassen möchten, müssen Sie derzeit beide Komponenten speziell mit Auswahlelementen ansteuern oder die benutzerdefinierte Eigenschaft direkt auf das Element mit dem Stilattribut anwenden. Das mag in Ordnung sein, aber es wäre hilfreicher, wenn die Entwickler diese Stile in einem übergeordneten Element oder sogar auf Stammebene definieren könnten.
Der Wert für die benutzerdefinierte Property muss direkt auf der Komponente festgelegt werden, da wir sie über die Auswahl für den Komponentenhost auf demselben Element definieren. Die globalen Design-Tokens, die wir direkt in der Komponente verwenden, werden direkt weitergeleitet, ohne von diesem Problem betroffen zu sein, und können sogar in übergeordneten Elementen abgefangen werden. Wie können wir das Beste aus beiden Welten nutzen?
Private und öffentliche benutzerdefinierte Properties
Private benutzerdefinierte Properties wurden von Lea Verou entwickelt. Dabei handelt es sich um eine kontextbezogene „private“ benutzerdefinierte Property an der Komponente selbst, die aber auf eine „öffentliche“ benutzerdefinierte Property mit einem Fallback festgelegt ist.
Wenn wir unsere benutzerdefinierten Kontexteigenschaften auf diese Weise definieren, können wir weiterhin alle Funktionen wie das Übernehmen globaler Tokenwerte und das Wiederverwenden von Werten im Komponentencode nutzen. Außerdem werden neue Definitionen dieser Eigenschaft von der Komponente oder einem übergeordneten Element übernommen.
Man könnte zwar argumentieren, dass diese Methode nicht wirklich „privat“ ist, aber wir sind der Meinung, dass dies eine ziemlich elegante Lösung für ein Problem ist, das uns Sorgen bereitet hat. Sobald wir die Gelegenheit dazu haben, werden wir dieses Problem in unseren Komponenten angehen, damit unser Entwicklungsteam mehr Kontrolle über die Komponentennutzung hat und gleichzeitig von den vorhandenen Sicherheitsmaßnahmen profitiert.
Ich hoffe, dass Ihnen dieser Einblick in die Verwendung von Webkomponenten mit benutzerdefinierten CSS-Eigenschaften gefallen hat. Lasst uns wissen, was ihr davon haltet. Wenn ihr eine dieser Methoden in eurer eigenen Arbeit verwenden möchtet, könnt ihr mich auf Twitter unter @DavidDarnes finden. Sie finden Nordhealth auch auf Twitter unter @NordhealthHQ sowie den Rest meines Teams, das hart daran gearbeitet hat, dieses Designsystem zusammenzustellen und die in diesem Artikel erwähnten Funktionen umzusetzen: @Viljamis, @WickyNilliams und @eric_habich.
Hero-Image von Dan Cristian Pădureț