Wiederverwendbare UI-Bausteine mit reinem JavaScript
Web Components sind eine Gruppe nativer Browser-APIs, mit denen sich gekapselte UI-Bausteine bauen lassen, die ohne Framework-Runtime funktionieren. Custom Elements, Shadow DOM und HTML Templates ermöglichen Komponenten, die in React, Vue, Angular und in reinem HTML gleichermaßen einsetzbar sind, ideal für Design-Systeme über Team- und Stack-Grenzen hinweg.
Inhaltsverzeichnis
- 1. Was Web Components wirklich sind
- 2. Custom Elements: Lifecycle-Callbacks und Registrierung
- 3. Shadow DOM: Kapselung von Styles und Markup
- 4. HTML Templates und Slots: Wiederverwendbare Strukturen
- 5. Attribute und Properties: Datenfluss steuern
- 6. Custom Events: Kommunikation nach außen
- 7. Styling-Strategien: Host, Parts und geteilte Stylesheets
- 8. Interoperabilität mit React, Vue und Angular
- 9. Web Components im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Was Web Components wirklich sind
Web Components sind kein einzelnes Feature, sondern eine Gruppe standardisierter Browser-APIs: Custom Elements zur Registrierung eigener HTML-Tags, Shadow DOM zur Kapselung von Markup und Styles, sowie HTML Templates zur deklarativen Wiederverwendung von Struktur. Der entscheidende Unterschied zu React- oder Vue-Komponenten liegt darin, dass Web Components ohne Framework-Runtime auskommen. Ein registriertes Custom Element funktioniert in jedem Browser, der die Spezifikation implementiert, unabhängig davon, welches Frontend-Framework die umgebende Anwendung nutzt.
Diese Framework-Unabhängigkeit macht Web Components besonders für Design-Systeme interessant, die über mehrere Teams mit unterschiedlichem Technologie-Stack hinweg funktionieren müssen. Ein Unternehmen, dessen eine Abteilung React einsetzt und eine andere Vue, kann eine gemeinsame Komponentenbibliothek als Web Components ausliefern, statt für jeden Stack eine eigene Implementierung zu pflegen. Das Custom Element <ms-button> verhält sich in beiden Umgebungen identisch, weil die Logik im Browser selbst lebt und nicht im Framework.
Die folgenden Abschnitte zeigen die praktische Umsetzung von Web Components im Detail: von der Registrierung eines Custom Elements über die Kapselung mit Shadow DOM bis zur Integration in bestehende Framework-Anwendungen. Jeder Abschnitt enthält lauffähigen Code, der ohne Build-Schritt direkt im Browser funktioniert, ein zentrales Versprechen von Web Components gegenüber kompilierten Framework-Komponenten.
2. Custom Elements: Lifecycle-Callbacks und Registrierung
Der Einstieg in Web Components beginnt mit der CustomElementRegistry, verfügbar über customElements.define(). Eine Klasse, die von HTMLElement erbt, wird unter einem Tag-Namen registriert, der zwingend einen Bindestrich enthalten muss, um Kollisionen mit zukünftigen nativen HTML-Elementen auszuschließen. Ab diesem Zeitpunkt erkennt der Browser das Tag im Markup und instanziiert die Klasse automatisch, sobald das Element im DOM erscheint, ganz ohne zusätzliches JavaScript-Framework.
Vier Lifecycle-Callbacks steuern das Verhalten jedes Custom Elements: connectedCallback läuft, sobald das Element in den DOM eingefügt wird, disconnectedCallback beim Entfernen, attributeChangedCallback bei Änderungen beobachteter Attribute, und adoptedCallback beim Verschieben in ein anderes Dokument. Diese Callbacks ersetzen die Lifecycle-Hooks, die man aus React oder Vue kennt, ohne dass eine externe Bibliothek nötig wäre. Genau das macht Web Components zur nativen Alternative zu Framework-spezifischen Komponentenmodellen.
// Registering a Web Component with full lifecycle handling
class MsCounter extends HTMLElement {
static observedAttributes = ['start'];
#count = 0;
connectedCallback() {
this.#count = Number(this.getAttribute('start') ?? 0);
this.render();
this.addEventListener('click', this.#handleClick);
}
disconnectedCallback() {
this.removeEventListener('click', this.#handleClick);
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'start' && oldValue !== newValue) {
this.#count = Number(newValue ?? 0);
this.render();
}
}
#handleClick = () => {
this.#count += 1;
this.render();
};
render() {
this.textContent = `Count: ${this.#count}`;
}
}
customElements.define('ms-counter', MsCounter);
3. Shadow DOM: Kapselung von Styles und Markup
Ohne Kapselung würden globale CSS-Regeln jede Komponente beeinflussen, ein Problem, das Web Components über Shadow DOM lösen. Mit this.attachShadow({ mode: 'open' }) erzeugt ein Custom Element einen eigenen DOM-Teilbaum, der von Styles außerhalb der Komponente unberührt bleibt und selbst keine Styles nach außen durchsickern lässt. Der Modus open erlaubt Zugriff über element.shadowRoot von außen, während closed diesen Zugriff verweigert, meist unnötig restriktiv für interne Design-Systeme.
Innerhalb des Shadow DOM gilt ein eigener Style-Kontext: ein <style>-Block im Shadow Root beeinflusst ausschließlich Elemente innerhalb dieses Baums. Das ist ein struktureller Vorteil von Web Components gegenüber CSS-Modulen oder CSS-in-JS-Lösungen in Frameworks, weil die Kapselung direkt vom Browser garantiert wird, nicht durch generierte Klassennamen. Für gemeinsame Basis-Styles über mehrere Instanzen hinweg bieten konstruierbare Stylesheets (CSSStyleSheet mit adoptedStyleSheets) eine performante Alternative zu doppelt eingebetteten <style>-Tags.
// Shadow DOM with a shared, constructable stylesheet
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
:host { display: inline-block; border-radius: 8px; }
.badge { padding: 4px 10px; font-family: sans-serif; }
`);
class MsBadge extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.adoptedStyleSheets = [sheet];
shadow.innerHTML = `<span class="badge"><slot></slot></span>`;
}
}
customElements.define('ms-badge', MsBadge);
4. HTML Templates und Slots: Wiederverwendbare Strukturen
Das <template>-Element ist ein inaktiver Markup-Block, dessen Inhalt vom Browser geparst, aber nicht gerendert wird, bis er per JavaScript geklont und eingefügt wird. In Kombination mit Custom Elements bildet <template> die deklarative Grundlage für die interne Struktur von Web Components, ohne dass Markup als String-Template zusammengesetzt werden muss. Das Klonen mit template.content.cloneNode(true) ist zudem performanter als wiederholtes innerHTML-Parsing.
<slot>-Elemente ermöglichen Content-Projektion: Kindelemente, die ein Nutzer der Komponente ins Licht-DOM schreibt, erscheinen an der Stelle des Slots im Shadow DOM, bleiben dabei aber weiterhin im ursprünglichen Licht-DOM verankert. Benannte Slots (<slot name="footer">) erlauben mehrere Einfügepunkte in einer einzigen Komponente. Dieses Muster aus Web Components entspricht funktional den Slots in Vue oder den children-Props in React, jedoch als natives Browser-Feature ohne Virtual-DOM-Vergleich.
// Template + named slots for a reusable card component
const template = document.createElement('template');
template.innerHTML = `
<style>
.card { border: 1px solid #e2e8f0; border-radius: 12px; padding: 16px; }
.footer { margin-top: 12px; font-size: 0.85em; color: #64748b; }
</style>
<div class="card">
<slot name="title"></slot>
<slot></slot>
<div class="footer"><slot name="footer"></slot></div>
</div>
`;
class MsCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));
}
}
customElements.define('ms-card', MsCard);
5. Attribute und Properties: Datenfluss steuern
HTML-Attribute sind immer Strings, während JavaScript-Properties beliebige Werte tragen können, ein Unterschied, den jede solide Implementierung von Web Components explizit behandeln muss. Das übliche Muster: static observedAttributes definiert, welche Attribute Änderungen auslösen, attributeChangedCallback reagiert darauf, und Getter/Setter auf der Klasse spiegeln Attribute in typisierte Properties. So kann element.setAttribute('start', '5') ebenso funktionieren wie element.start = 5, je nachdem, ob die Komponente deklarativ im Markup oder imperativ per JavaScript konfiguriert wird.
Komplexe Datenstrukturen wie Arrays oder Objekte lassen sich nicht sinnvoll als Attribute serialisieren, deshalb setzen viele Web Components-Implementierungen für solche Fälle ausschließlich auf Properties, während primitive Konfigurationswerte über Attribute laufen. Diese Trennung, Attribute für einfache, serialisierbare Werte und Properties für komplexe Daten, ist eine der wichtigsten Konventionen bei der Arbeit mit Web Components und wird auch von Bibliotheken wie Lit konsequent umgesetzt.
6. Custom Events: Kommunikation nach außen
Da Web Components keinen zentralen State-Container besitzen, findet Kommunikation nach außen über das native CustomEvent-Interface statt. Ein Custom Element dispatcht ein Event mit this.dispatchEvent(new CustomEvent('ms-change', { detail: value, bubbles: true, composed: true })), und der umgebende Code, egal ob reines HTML, React oder Vue, registriert einen regulären addEventListener. Das Flag composed: true ist entscheidend, weil es dem Event erlaubt, die Shadow-DOM-Grenze zu überqueren und im Licht-DOM sichtbar zu werden.
Diese Event-basierte Kommunikation macht Web Components zu guten Bürgern in jeder Umgebung: React-Entwickler binden Events über Refs und addEventListener, weil JSX Custom Events standardmäßig nicht wie native DOM-Events behandelt. Vue dagegen unterstützt Custom Events aus Web Components bereits nativ über die reguläre v-on-Syntax. Wer eine Komponentenbibliothek als Web Components plant, sollte Events konsequent dokumentieren, weil sie die einzige öffentliche Ausgangsschnittstelle jenseits von Properties darstellen.
// Dispatching a custom event that crosses the shadow boundary
class MsToggle extends HTMLElement {
connectedCallback() {
this.addEventListener('click', () => {
const next = this.getAttribute('checked') !== 'true';
this.setAttribute('checked', String(next));
this.dispatchEvent(new CustomEvent('ms-toggle', {
detail: { checked: next },
bubbles: true,
composed: true, // crosses shadow DOM boundary
}));
});
}
}
customElements.define('ms-toggle', MsToggle);
// Consumer code, framework agnostic
document.querySelector('ms-toggle')
.addEventListener('ms-toggle', (event) => {
console.log('Toggle state:', event.detail.checked);
});
7. Styling-Strategien: Host, Parts und geteilte Stylesheets
Der Pseudo-Selektor :host stylt das Custom Element selbst von innerhalb des Shadow DOM, während :host(.aktiv) auf Klassen reagiert, die von außen gesetzt werden. Für Fälle, in denen Nutzer einer Komponente gezielt interne Elemente stylen sollen, ohne die Kapselung vollständig aufzugeben, bietet das part-Attribut zusammen mit dem CSS-Selektor ::part(name) einen kontrollierten Ausbruch aus dem Shadow DOM. Dieses Muster erlaubt Web Components, gezielt Anpassbarkeit zu gewähren, ohne die interne Struktur komplett offenzulegen.
Für Design-Tokens, die über viele Instanzen hinweg konsistent bleiben sollen, sind CSS Custom Properties (--ms-primary-color) der bevorzugte Weg, weil sie im Gegensatz zu normalen Styles die Shadow-DOM-Grenze durchdringen und vom umgebenden Dokument gesetzt werden können. Konstruierbare Stylesheets über adoptedStyleSheets reduzieren zusätzlich den Speicherbedarf, wenn tausend Instanzen derselben Komponente auf einer Seite existieren, weil das Stylesheet nur einmal geparst statt tausendfach dupliziert wird.
// :host, ::part and CSS custom properties for controlled styling
class MsPanel extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
:host { --ms-primary-color: #0f172a; display: block; }
.header { background: var(--ms-primary-color); color: white; padding: 12px; }
</style>
<div class="header" part="header"><slot name="header"></slot></div>
<div part="body"><slot></slot></div>
`;
}
}
customElements.define('ms-panel', MsPanel);
8. Interoperabilität mit React, Vue und Angular
React hatte lange Zeit Schwierigkeiten, Properties und Custom Events von Web Components sauber zu behandeln, weil JSX standardmäßig Attribute statt Properties setzt und Custom Events nicht automatisch wie synthetische Events verkabelt. Der übliche Workaround ist ein ref, über den Properties direkt gesetzt und addEventListener manuell registriert wird. Neuere React-Versionen verbessern die native Unterstützung, doch bei komplexen Web Components bleibt ein dünner Adapter-Layer meist die zuverlässigere Lösung.
Vue unterstützt Web Components deutlich direkter: mit compilerOptions.isCustomElement weiß der Compiler, welche Tags keine Vue-Komponenten sind, und Properties sowie Events aus Custom Elements werden über die reguläre Template-Syntax gebunden. Angular verlangt das Schema CUSTOM_ELEMENTS_SCHEMA im jeweiligen Modul, danach funktionieren Web Components wie natives HTML innerhalb von Angular-Templates. Für die Autorenseite erleichtert die Bibliothek Lit den Bau robuster Web Components erheblich, ohne dabei von der zugrunde liegenden Browser-API abzuweichen.
9. Web Components im Vergleich
Die Entscheidung zwischen Web Components und Framework-Komponenten ist selten eine reine Geschmacksfrage, sondern hängt vom Verwendungskontext ab: eine geteilte Komponentenbibliothek über mehrere Stacks hinweg profitiert stark von Web Components, während eine einzelne React-Anwendung ohne Interop-Bedarf meist besser mit nativen React-Komponenten bedient ist.
| Kriterium | Web Components | Framework-Komponenten | Praxis-Hinweis |
|---|---|---|---|
| Laufzeit-Abhängigkeit | Keine, nativer Browser | Framework-Runtime nötig | Wichtig für geteilte Design-Systeme |
| Styling-Kapselung | Shadow DOM, garantiert | CSS-Module oder CSS-in-JS | Shadow DOM verhindert Leaks strukturell |
| Server-Side Rendering | Eingeschränkt, Declarative Shadow DOM nötig | Ausgereift (Next.js, Nuxt) | SSR-lastige Seiten oft mit Framework schneller |
| Cross-Framework-Nutzung | Nativ, überall gleich | Nur im eigenen Ökosystem | Entscheidend bei Multi-Team-Design-Systemen |
| Entwicklungs-Ergonomie | Verbessert durch Lit | Ausgereiftes Tooling und DevTools | Framework meist produktiver im Alltag |
In der Praxis entstehen die stärksten Vorteile von Web Components dort, wo Wiederverwendbarkeit über Team- und Technologiegrenzen hinweg wichtiger ist als maximale Entwicklungsgeschwindigkeit innerhalb eines einzelnen Frameworks. Design-Systeme großer Organisationen setzen deshalb zunehmend auf Web Components als gemeinsame Basis, ergänzt um Framework-spezifische Wrapper für komfortablere Integration.
Mironsoft
Frontend-Architektur, Design-Systeme und framework-übergreifende Komponenten
Ein Design-System, das jedes Frontend-Team nutzen kann?
Wir bauen Web Components, die als gemeinsame Basis über React, Vue und reines HTML funktionieren, mit Shadow DOM, klaren Attribut-Property-Konventionen und dokumentierten Custom Events.
Komponenten-Audit
Analyse bestehender UI-Bausteine auf Wiederverwendbarkeit über Stacks hinweg
Web-Components-Bibliothek
Aufbau eines Design-Systems mit Custom Elements, Shadow DOM und Lit
Framework-Integration
Adapter für React, Vue und Angular für nahtlose Nutzung bestehender Komponenten
10. Zusammenfassung
Web Components sind die native Antwort des Browsers auf wiederverwendbare, gekapselte UI-Bausteine, ohne dass eine Framework-Runtime mitgeliefert werden muss. Custom Elements registrieren eigene Tags mit klaren Lifecycle-Callbacks, Shadow DOM garantiert Style-Kapselung auf Browser-Ebene, und HTML Templates mit Slots liefern eine deklarative Struktur für wiederverwendbare Inhalte. Custom Events übernehmen die Kommunikation nach außen, dort wo Framework-Komponenten normalerweise auf Props-Callbacks oder Store-Bindings setzen.
Der größte Hebel von Web Components liegt in der Cross-Framework-Wiederverwendbarkeit: eine einmal gebaute Komponente funktioniert in React, Vue, Angular und in reinem HTML, ohne Portierung. Für Organisationen mit mehreren Frontend-Teams und unterschiedlichen Technologie-Entscheidungen ist das ein struktureller Vorteil, den kein Framework-eigenes Komponentenmodell bieten kann. Bibliotheken wie Lit reduzieren dabei den Boilerplate-Aufwand, ohne von der zugrunde liegenden Web Components-Spezifikation abzuweichen.
Web Components ohne Framework — Das Wichtigste auf einen Blick
Custom Elements
customElements.define() registriert eigene Tags mit Lifecycle-Callbacks wie connectedCallback und attributeChangedCallback.
Shadow DOM
attachShadow({ mode: 'open' }) kapselt Styles und Markup strukturell, keine globalen CSS-Leaks.
Templates & Slots
<template> und <slot> liefern deklarative Struktur und Content-Projektion ohne Virtual DOM.
Interoperabilität
Funktioniert in React (mit Adapter), Vue (nativ) und Angular (CUSTOM_ELEMENTS_SCHEMA) gleichermaßen.