Web Components: Erklärung, Einsatzszenarien & Anwendung

Veröffentlicht 17.01.2025

Veit Kunz Software Enginner d.velop AG

Beitragsbild Blogartikel Web Components

Komponenten sind aus der modernen Webentwicklung nicht mehr wegzudenken. In allen JavaScript-Frameworks mit etwas größerer Verbreitung, wie Svelte oder Vue, lässt sich der Code in wiederverwendbaren Komponenten organisieren. Alle diese Frameworks haben jedoch gemeinsam, dass die Implementierung der jeweiligen Komponenten nur innerhalb des jeweiligen Frameworks funktioniert. Es ist nicht ohne weiteres möglich, eine Svelte-Komponente in einer Vue-Anwendung zu benutzen oder umgekehrt. Daher ist es nicht verwunderlich, dass sich die Köpfe hinter den Webstandards eine Lösung überlegt haben, wie sich Komponenten unabhängig von bestimmten Frameworks entwickeln und nutzen lassen. In diesem Prozess sind diverse Standards für HTML, DOM, CSS und JavaScript entstanden, die heute allgemein unter dem Begriff „Web Components“ zusammengefasst werden.

Einsatz von Web Components in der Entwicklung bei d.velop

Bei d.velop schauen wir ebenfalls gespannt auf diese Entwicklung. Seit einiger Zeit sind Web Components ein fester Teil in der Planung zukünftiger Features. Insbesondere wenn es um Themen wie eine zentrale Component Library oder Micro-Frontends geht, sind Web Components nicht mehr aus den Köpfen zu bekommen. Da wir diverse Frontend-Frameworks in verschiedenen Bereichen einsetzen, sind Web Components die ideale Lösung für eine umfassende Interoperabilität zwischen den Teams.

Das lernst du in diesem Artikel über Web Components

In diesem Blogartikel…

  • erhältst du eine Einführung in das Thema der Komponenten, bis hin zu Web Components.
  • erfährst du, welche Standards das bilden, was wir als Web Components bezeichnen.
  • lernst du, wie du eigene Web Components erstellen kannst, die du auf jeder beliebigen Webseite einbinden und benutzen kannst.
  • erlangst eine Einschätzung, für welche Zwecke Web Components geeignet sind und wo du besser doch noch auf die klassischen Frameworks setzen solltest.

Entstehungsgeschichte: Der lange Weg zu Komponenten

Wer Anfang der 2000er Jahre oder früher mit Webentwicklung angefangen hat, wird sich über Komponenten vermutlich noch wenige Gedanken gemacht haben. Zumindest gab es keine (allgemein bekannten) Frameworks, die solche Komponenten unterstützten, wie wir sie heute kennen. Zu Zeiten, in denen jQuery noch der Standard der Webentwicklung war, hat man JavaScript allenfalls über Dateien modularisiert. Das HTML der Webseiten wurde ohnehin über Lösungen wie PHP oder JavaEE gerendert, weswegen es für Frontend-Komponenten gar nicht den großen Bedarf gab wie heute.

AngularJS erblickte 2010 das Licht der Welt

Während die Fähigkeiten von JavaScript mit der Zeit immer weiter zunahmen, wanderte nach und nach auch immer mehr Logik vom Server in den Browser. Als schließlich 2010 AngularJS das Licht der Welt erblickte, rollte so langsam die Welle los, auf serverseitiges Rendern vom HTML zu verzichten. Alles wurde nun im Browser gemacht. Der Server diente nur noch dazu, die statischen Assets auszuliefern und Daten hin- und herzuschicken. Mit dem Erfolg weiterer Frameworks, wie React und Vue, wurden immer mehr individuell entwickelten Webseiten nur noch browserseitig gerendert.

Was viele sicherlich jedoch nicht wissen, ist, dass die Entwickler der Webstandards auch schon sehr früh in diesem Prozess über das Thema Komponenten bzw. Modularisierung nachgedacht haben. Der erste Standard in diesem Bereich, Shadow DOM v0, wurde bereits 2011 von den ersten Browsern unterstützt. Auch wenn diese ersten Standards, Shadow DOM v0, HTML Imports und Custom Elements v0, heute schon lange durch neue und bessere Standards ersetzt wurden, sah man so sehr früh die Bestrebungen, JavaScript zu einer vollwertigen Browser-Programmiersprache auszubauen. Und Teil dieser Bestrebungen war dann auch die Möglichkeit, Bestandteile von Webseiten mit Komponenten zu modularisieren.

Was sind Web Components?

Der Begriff Web Components ist im Grunde eine Art Marketingbegriff. Für sich selbst ist das kein Webstandard. Dahinter verbirgt sich ein Konzept, welches sich aus mehreren Webstandards zusammensetzt. Mit einer Web Component kannst du eigene HTML-Elemente definieren, die du wie jedes andere HTML-Element verwenden kannst. Dabei beinhaltet das neue HTML-Element alle notwendigen Teile, von eigenem HTML-Inhalt, über Styling, bis zu JavaScript-Logik. Dabei sind die Inhalte genauso von der restlichen Webseite abgekapselt, wie zum Beispiel die interne Struktur eines <input>-Elements.

Unter Web Components fallen diverse Webstandards, von denen das hier die wichtigsten sind:

  • Custom Elements
  • Shadow DOM
  • HTML Template

Die wichtigsten Webstandards erklärt

Custom Elements

Custom Elements sind die zentrale Schnittstelle, damit du aus einer JavaScript-Klasse eine Web Component machen kannst. Dazu musst du erst einmal nur zwei Dinge tun. Als Erstes leite deine Klasse von HTMLElement ab. Als Zweites definiere für die Klasse ein Tag, welches im HTML verwendet werden kann. Dabei muss das Tag nach Spezifikation mindestens einen Bindestrich beinhalten, damit es keine Kollisionen mit den Standard-Tags gibt. Am besten siehst du das in dem Codebeispiel.

Codebeispiel 👨‍💻

class MyComponent extends HTMLElement { /* ... */ }

window.customElements.define('my-component', MyComponent);


Innerhalb der Klasse hast du jetzt verschiedene Möglichkeiten, festzulegen, was genau in deiner Komponente für Inhalt gerendert werden soll und wie dieser gestylet werden soll. Außerdem kannst du auf verschiedene Browserevents reagieren und deine Web Component perfekt für Accessibility und als Formularelemente konfigurieren. Mehr dazu findest du weiter unten.

Shadow DOM

Ein Shadow DOM ist ein besonderer Bereich im normalen DOM des Browsers. Normalerweise kannst du über Funktionen wie document.querySelector() alle Elemente im DOM finden und manipulieren. Inhalte von einem Shadow DOM sind dafür jedoch unsichtbar. Wenn du dort Inhalte finden und manipulieren willst, brauchst du die Instanz des jeweiligen Shadow DOMs. Solltest du mehrere Shadow DOMs haben, sogar verschachtelt, dann sind diese auch untereinander vollständig unabhängig. Web Components setzen grundlegend auf Shadow DOM, um Inhalte zu rendern. Alle Inhalte, die du innerhalb deiner Komponente rendern willst, renderst du innerhalb eines eigenen Shadow DOM Bereichs. Das Codebeispiel zeigt, wie du einen Shadow DOM in deiner Komponente erzeugen und benutzen kannst.

Codebeispiel 👩‍💻

this.attachShadow({mode: 'open'});

this.shadowRoot?.appendChild(element);

this.shadowRoot?.querySelector('element');


HTML Templates

HTML Templates sind eine Art Kopiervorlage für HTML-Blöcke, die selbst erst einmal nicht gerendert werden. Wenn der Inhalt deiner Web Component dynamische Teile beinhaltet, kannst du die entsprechenden HTML-Bestandteile in HTML Templates verpacken und so über JavaScript dynamisch zum sichtbaren DOM hinzufügen. Dadurch sparst du dir eine Menge document.createElement() Aufrufe.

Codebeispiel 👨‍💻

<template>

  HTML Snippet

</template>


Wie erstelle ich eine eigene Web Component?

Der Lebenszyklus einer Komponente

Nachdem du jetzt die drei grundlegenden Webstandards kennengelernt hast, kannst du tiefer darin einsteigen, deine eigene Web Component zu schreiben. Wie schon erwähnt, sind Web Components JavaScript-Klassen, die von HTMLElement erben. Dabei werden dir als Entwickler mehrere sogenannte Lifecycle-Callbacks bereitgestellt, mit denen du auf verschiedene Events reagieren kannst.

Codebeispiel 👩‍💻

class MyButtonComponent extends HTMLElement {

    constructor() {

        super();

    }

    connectedCallback() {}

    disconnectedCallback() {}

    adoptedCallback() {}

    attributeChangedCallback(name, oldValue, newValue) {}

}

window.customElements.define('my-button-component', MyButtonComponent);

Der Konstruktur wird aufgerufen, sobald eine neue Instanz der Komponente, ein Element, erstellt wird. An dieser Stelle ist die Komponente jedoch noch nicht zum DOM hinzugefügt worden. Außerdem kannst du hier noch nicht mit den Attributen des Elements arbeiten. Aber das ist der beste Zeitpunkt, um den Shadow DOM zu initialisieren und die interne HTML-Struktur aufzubauen. Diese Stelle hat den Vorteil, dass noch kein anderer Code außerhalb mit dem Element interagieren konnte. Das ist zum Beispiel dann relevant, wenn jemand das Element dynamisch über "document.createElement('my-input')" oder „new MyInputComponent()“ erzeugt. Dann könnte der „externe“ Code schon versuchen, mit deiner Komponente zu interagieren, bevor die Komponete Teil vom DOM geworden ist und der connectedCallback() aufgerufen wurde.

Der connectedCallback() und disconnectedCallback() wird aufgerufen, wenn das Element zum DOM hinzugefügt bzw. entfernt wird. Im connectedCallback() kannst du auch erstmals mit den Attributen deines Elements interagieren. Der adoptedCallback() ist ein Spezialfall, der nur dann relevant wird, wenn du ein Element zwischen verschiedenen Frames verschiebst.

Der attributeChangedCallback() wird dann wichtig, wenn deine Komponente Attribute haben soll. Dieser Callback wird bei jeder Änderung der Attribute aufgerufen, die du dafür registriert hast. Setze dazu in der Klasse das statische Feld "observedAttributes", entweder als Variable oder als getter. Beachte jedoch, das HTML bei Attributen nur Kleinschreibung erlaubt.

Codebeispiel 👨‍💻

static observedAttributes = ['placeholder''disabled'];

static get observedAttributes() {

    return ['placeholder''disabled'];

}

Template und Styles definieren

Deine Web Component soll natürlich auch einen Inhalt haben, und mit entsprechendem Styling versehen sein. Dazu bieten dir die Custom Elements verschiedene Wege, um das abzubilden. Der einfachste Weg, Inhalt zu deiner Komponente hinzuzufügen, ist, dass du die einzelnen Elemente über document.createElement() erzeugst und dann zum Shadow DOM hinzufügst, so wie es oben in dem Shadow DOM Beispiel gezeigt wurde. Wenn deine Komponente nur wenig Inhalt hat, ist das auch ein wunderbarer Weg, aber spätestens bei komplexeren Inhalten, wird es umständlich. Hier kannst du ganz einfach auf die innerHTML-Eigenschaft vom Shadow DOM zurückgreifen und so ein komplettes HTML-Snippet dem shadowRoot hinzufügen. Du kannst auch ein slot-Element zu dem Inhalt hinzufügen. Wenn jemand deine Komponente benutzt, und weiteres HTML zwischen das Start- und End-Tag schreibt, wird dieser Abschnitt im DOM an der Stelle des Slots dynamisch eingefügt und gerendert. Um mehrere unabhängige Slot-Bereiche in der Komponente zu definieren, kannst du sogenannte named-Slots verwenden

Um Styles zu definieren, gibt es drei verschiedene Varianten. Die ersten beiden Varianten kennst du auch schon, wenn du ganz normal HTML schreibst. Als Erstes kannst du ein link-Tag mit einer CSS-Datei in den shadowRoot einfügen, so wie du es sonst im head-Bereich einer HTML-Seite machen würdest. So kannst du auch dieselben Styles in mehrere Web Components einfügen. Als Zweites kannst du in dein shadowRoot ein einfaches style-Tag einfügen und darin die Styles definieren. Sauberer als ein style-Tag ist aber der dritte Weg, der über die sogenannte Adopted Stylesheets funktioniert. Adopted Styles Sheets sind eine Möglichkeit, CSS zu deiner Komponente hinzuzufügen, ohne das diese unmittelbar Bestandteil des DOM sind, aber trotzdem für das Styling angewendet werden. Du kannst das Vergleichen mit den Standard-Styles, die jeder Browser für zum Beispiel Überschriften oder Absätze mitbringt.

In diesem Codebeispiel siehst du die Verwendung von HTML über innerHTML und die Adopted Styles Sheets.

Codebeispiel 👩‍💻

class MyButtonComponent extends HTMLElement {

    constructor() {

        super();

        this.attachShadow({mode: 'open'});

        const stylesheet = new CSSStyleSheet();

        stylesheet.replaceSync(`

            :host {

                display: inline;

            }

            button {

                color: green;

            }

        `);

        this.shadowRoot.adoptedStyleSheets = [stylesheet];

        this.shadowRoot.innerHTML = `

            <button><slot></slot></button>

            <slot name="otherSlot"></slot>

        `;

    }

}

window.customElements.define('my-button-component', MyButtonComponent);

<my-button-component>

  Hello, World! <!-- Default Slot -->

  <span slot="otherSlot">Foo Bar</span>  <!-- Named Slot -->

</my-button-component>

Die Web Component als Formularelement

Wenn deine Web Component ein Formularelement darstellen soll, etwa ein spezielles Eingabefeld, kannst du deine Komponente so erweitern, dass sie sich in ein HTML-Formular integriert. Wenn du ein HTML-Formular auf deiner Webseite hast und der Anwendedne das Formular abschickt, passieren im Hintergrund mehrere Dinge. Als Erstes prüft der Browser, ob es für die Formularelemente Validierungsregeln gibt und wertet diese aus. Wenn eine Validierung fehlschlägt, wird dem Anwendenden eine entsprechende Fehlermeldung angezeigt. Passt alles, sammelt der Browser als Nächstes die Inhalte von allen Formularfeldern und schickt diese an den definierten Endpunkt, oder die Formularinhalte lassen sich per JavaScript abrufen.

In diesem Prozess kannst du dich mit deiner Komponente einhängen. Als Erstes musst du in der Klasse deiner Komponente eine statische Variable mit dem Namen "formAssociated" definieren und auf "true" setzen. Außerdem musst du dir im Konstruktur eine Instanz der ElementInternals holen und in einer privaten Variable der Klasse speichern, das geht über "this.#internals = this.attachInternals()". Zudem musst du bei der Verwendung deiner Komponente in einem Formular natürlich das name-Attribut setzen.

Innerhalb deiner Web Component kannst du jetzt über "this.#internals.setFormValue(…)" festlegen, welchen „value“ deine Komponente als Formularelement aktuell hat. Dieser Value wird beim Absenden des Formulars verwendet. Über "this.#internals.setValidity(…)" legst du fest, ob der aktuelle Value gültig ist oder nicht. Das ist wichtig für die automatische Validierung des Browsers. Wenn du als einzigen Parameter ein leeres Objekt übergibst, bedeutet das, dass der Value die Validierung besteht. Um eine fehlgeschlagene Validierung zu melden, brauchst du drei Parameter. Der erste Parameter, ein Objekt, zeigt an, von welchem Typ der Fehler ist. Als zweiter Parameter wird ein Text angegeben, der dem Anwender angezeigt wird. Der dritte Parameter ist ein Element innerhalb des shadowRoot, an welchem der Browser die Validierungsmeldung anzeigt. Welche Inhalte für den ersten Parameter erlaubt sind, kannst du in den MDN Web Docs nachlesen.

Normalerweise zeigt der Browser Validierungsmeldungen nur an, wenn der Anwender das Formular absenden will. Du kannst den Browser jedoch auch anweisen, die Validierungsmeldung direkt anzuzeigen. Dafür musst du lediglich die Funktion "this.#internals.reportValidity()" ohne Parameter aufrufen.

Barrierefreiheit für Web Components

Das Thema Barrierefreiheit wurde in der Webentwicklung lange Zeit gar nicht oder nur sehr widerwillig behandelt, auch wenn Menschen mit Beeinträchtigungen schon immer eine relevante Nutzergruppe des Internets waren. Spätestens seit Vorgaben wie der BITV 2.0 oder dem Barrierefreiheitsstärkungsgesetz (European Accessibility Act) müssen sich jedoch alle Webentwickler mit diesem Thema auseinandersetzen. HTML und JavaScript haben schon lange viele Optionen, Webseiten so zu gestalten, dass sie etwa einwandfrei per Tastatursteuerung oder mittels Screenreader bedienbar sind. Das sind nur zwei von mehreren Themen der Barrierefreiheit.

Auch Web Components bieten dir natürlich einige Möglichkeiten für Barrierefreiheit an. Wenn es um Themen wie Styling oder Tastatursteuerung geht, hast du in deiner Komponente die Möglichkeiten, die du auch sonst in HTML und CSS hast. Gerade beim Thema Screenreader sind Definitionen für ARIA (Accessible Rich Internet Applications) wichtig, also zum Beispiel sowas wie "aria-disabled" oder "aria-label". Mit diesen Definitionen kannst du genau festlegen, was ein bestimmtes Element bedeutet und welchen Status es aktuell hat. Es geht dabei um Informationen, die für normalsehende Menschen in der Regel visuell erfassbar sind, aber für Screenreader nicht sichtbar sind. Zum Beispiel kann ein Screenreader nicht vorlesen, ob ein Input-Element gerade deaktiviert ist, wenn dieser Status nur über JavaScript und CSS geregelt wird. Während das für Standard-Elemente browserseitig definiert ist, musst du dich bei eigenen Komponenten selbst darum kümmern und ARIA-Definitionen verwenden. Auch wenn deine Web Component zum Beispiel einen Button darstellt, weiß der Screenreader nicht, dass das ein Button sein soll. Um das zu definieren, musst du zum Beispiel das Attribut role="button" an deiner Komponente setzen. Eine Liste aller Rollen findest du in den MDN Web Docs. Neben der role gibt es auch noch weitere ARIA-Attribute, mit denen du dem Screenreader zum Beispiel mitteilen kannst, dass deine Komponente aktuell deaktiviert ist. Oder wenn deine Komponente einen Bereich hat, der sich auf- und zuklappen lässt, kannst du diesen Status auch über ARIA-Attribute bekannt machen. Eine Liste der verfügbaren Attribute findest du auch hier wieder in den MDN Web Docs.

Wie geht das aber jetzt in einer Web Component?

Für die role musst du leider, nach offizieller Spezifikation, innerhalb deiner Komponente direkt das role-Attribut im DOM setzen. Dazu prüftst du im "connectedCallback()", ob nicht schon von außen eine role gesetzt wurde, und setzt dann deine role per "this.role = 'button'". Für die anderen ARIA-Attribute kannst du wieder die ElementInternals benutzen, die du auch für Formularlemente brauchst. Mit den ElementInternals kannst du auch schon im Konstruktur Aufrufe wie "this.#internals.ariaDisabled = 'true'" ausführen, um dem Screenreader mitzuteilen, dass die Komponente gerade deaktiviert ist. Du kannst die ARIA-Attribute natürlich auch an jeder anderen Stelle, zum Beispiel in den Lifecycle-Callbacks, innerhalb deiner Komponente setzen.

Codebeispiel 👨‍💻

class MyButtonComponent extends HTMLElement {

    #internals;

    constructor() {

        super();

        this.attachShadow({mode: 'open'});

        this.shadowRoot.innerHTML = `<slot></slot>`;

        this.#internals = this.attachInternals();

        this.#internals.ariaDisabled = 'true';

    }

    connectedCallback() {

        if (this.role === null) {

            this.role = 'button';

        }

    }

}

window.customElements.define('my-button-component', MyButtonComponent);

Wofür benötige ich Web Components?

Sind jetzt Web Components die alleinige Zukunft der Webentwicklung? Vermutlich nicht. Die bekannten Frameworks wie Svelte oder Vue oder zukünftige Alternativen werden sicherlich weiterhin die individuelle Webentwicklung dominieren. Diese Frameworks haben Vorteile, die sich mit Web Components aktuell nicht oder nur schwer abbilden lassen. Zum Beispiel ist das Thema Server-Side Rendering schwierig. Meta-Frameworks wie SvelteKit und Vue arbeiten für Server-Side Rendering nicht mit einem DOM im Backend, welcher für das Rendering von Web Components jedoch zwingend notwendig ist. Das Framework Lit hat zwar eine (zum Veröffentlichungszeitpunkt des Artikels) experimentelle Funktion, um mit einem abgespeckten DOM Web Components server-seitig zu rendern. Aber diese Funktion müsste erst noch als Plugin in die Compile- oder Render-Pipelines der bestehenden Meta-Frameworks integriert werden, und das funktioniert natürlich nur mit Web Components, die auch mit Lit erstellt wurden. Aber auch andere Funktionen, wie verschiedene Optimierungen für Laufzeit und Performance, sind mit Web Components schwerer abbildbar. Solltest du Web Components jetzt lieber doch nicht verwenden?

Es kommt ganz auf deinen Einsatzzweck an.

Web Components sind vollkommen unabhängig vom verwendeten Framework. Egal, ob du Angular, React, Svelte, Vue, Solid, andere oder gar kein Framework einsetzt, kannst du Web Components verwenden. Das macht Web Components zu dem perfekten Werkzeug, wenn du etwa eine übergreifende Komponentenbibliothek entwickeln möchtest. Anstatt die Library mehrfach für verschiedene Frameworks zu entwickeln, brauchst du nur einmal Web Components bereitzustellen. Oder ihr habt verschiedene Teams, die mit verschiedenen Frameworks arbeiten, aber Komponenten für eine zentrale oder übergreifende Nutzung bereitstellen sollen. Viele moderne Frameworks bieten auch an, ihre Komponenten als Web Components zu exportieren. Als Beispiel lassen sich die „Material Web Components“ nennen, eine Komponentenbibliothek von Google für Material Design. Hier stellt Google Web Components bereit, die direkt in jedem Projekt verwendet werden können, und außerdem mit einem Framework entwickelt wurden.

Das Allheilmittel für Webentwicklung sind Web Components nicht.

Dennoch haben sie ihre Daseinsberechtigung und werden zukünftig sicherlich immer häufiger zu sehen sein. Mit den Möglichkeiten, die du jetzt schon mit Web Components hast, kannst du auch bereits die meisten Anwendungsfälle, für die Komponenten gebraucht werden, abbilden. In Kombination mit weiteren neuen und kommenden Web-Features sind Web Components auf jeden Fall ein wichtiger Meilenstein auf dem Weg in eine Webentwicklungs-Welt, in der natives und standardisiertes JavaScript, HTML und CSS wieder mehr in den Fokus rückt.

Dein Job in der Entwicklung bei d.velop
wartet auf Dich!

Entwickeln ist bei uns nicht einfach nur ein Job, sondern eine Leidenschaft. Wir stehen hinter unserem Code, den wir zusammen mit über 200 Personen in unseren agilen, crossfunktionalen Teams in der Entwicklung schaffen.