Web Components ohne Framework: Wiederverwendbare UI-Bausteine mit reinem JavaScript
AI generated
JS
() =>
JavaScript · Web Components · Custom Elements · Framework agnostisch
Web Components ohne Framework
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.

18 Min. Lesezeit Custom Elements · Shadow DOM · Templates · Slots Alle modernen Browser

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.

11. FAQ: Web Components ohne Framework

1Was sind Web Components genau?
Native Browser-APIs, Custom Elements, Shadow DOM und Templates, für gekapselte, wiederverwendbare UI-Bausteine ohne Framework-Runtime.
2Brauche ich Lit für Web Components?
Nein, reines JavaScript reicht. Lit reduziert Boilerplate, ist aber optional.
3Funktionieren Web Components in React?
Ja, mit Ref-basiertem Adapter für Properties und addEventListener für Custom Events.
4Shadow DOM vs. CSS-Module?
Shadow DOM kapselt garantiert auf Browser-Ebene, CSS-Module nur über eindeutige Klassennamen zur Build-Zeit.
5Wann mode: closed verwenden?
Selten, erschwert Debugging. mode: open ist für die meisten Design-Systeme praktischer.
6Wie kommunizieren Web Components nach außen?
Über CustomEvent mit bubbles und composed, damit das Event die Shadow-Grenze überquert.
7Attribute vs. Properties?
Attribute sind Strings im Markup, Properties beliebige Werte. Getter/Setter spiegeln zwischen beiden.
8Unterstützt Vue Web Components nativ?
Ja, über compilerOptions.isCustomElement, ohne zusätzlichen Adapter.
9Vorteil von adoptedStyleSheets?
Ein Stylesheet wird einmal geparst und über viele Instanzen geteilt statt dupliziert.
10Eignen sich Web Components fuer SSR?
Eingeschraenkt ueber Declarative Shadow DOM, Oekosystem weniger ausgereift als Next.js oder Nuxt.