ElementInternals API: eigene Custom Elements formularfaehig machen
AI generated
JS
() =>
JavaScript · Web Components · Formulare
ElementInternals API
Eigene Custom Elements genauso formularfaehig machen wie ein natives input-Element

Ohne ElementInternals konnte ein selbst gebautes Custom Element nie wirklich an einem form-Element teilnehmen: kein automatischer Eintrag in FormData, keine native Validierung, kein :invalid-Styling. Die ElementInternals API schliesst diese Luecke ueber attachInternals(), setFormValue() und setValidity() und macht aus einem gewoehnlichen Custom Element ein vollwertiges Formular-Steuerelement.

15 Min. Lesezeit attachInternals() · setFormValue() Formulare · Validierung · Accessibility

1. Das Formular-Problem klassischer Custom Elements

Ein Custom Element ohne besondere Vorkehrungen ist fuer ein umgebendes form-Element schlicht unsichtbar. Sein Wert landet nicht automatisch in einem FormData-Objekt, es nimmt nicht an der nativen Constraint-Validation teil, und CSS-Pseudoklassen wie :invalid oder :required greifen nicht, weil der Browser das Element schlicht nicht als Formular-Steuerelement kennt, egal wie sehr es optisch wie ein input aussieht.

Die frueher uebliche Loesung bestand darin, innerhalb des Custom Elements ein verstecktes natives input-Element zu erzeugen und dessen value synchron zu halten, ein fehleranfaelliger Workaround mit doppelter Zustandshaltung und zusaetzlichem DOM-Gewicht. Die ElementInternals API ersetzt diesen Trick durch eine direkte, native Anbindung des Custom Elements selbst an die Formular-Infrastruktur des Browsers.

2. attachInternals(): Die Bruecke zwischen Custom Element und Formular

Der erste Schritt ist die statische Eigenschaft static formAssociated = true auf der Element-Klasse, mit der ein Custom Element dem Browser signalisiert, dass es an Formularen teilnehmen moechte. Erst danach liefert this.attachInternals() im Konstruktor ein ElementInternals-Objekt zurueck, das als zentrale Schnittstelle fuer alle folgenden Formular-Operationen dient und typischerweise einmalig in einer privaten Instanzvariable gespeichert wird.

attachInternals() darf pro Element nur einmal aufgerufen werden, ein zweiter Aufruf wirft einen Fehler. Ab diesem Zeitpunkt erkennt der Browser das Element automatisch als Bestandteil des umschliessenden form-Elements, einschliesslich der Aufnahme in die form.elements-Sammlung, ganz ohne dass ein zusaetzliches natives Element im DOM noetig waere.


class BewertungsFeld extends HTMLElement {
  static formAssociated = true;

  #internals;

  constructor() {
    super();
    this.#internals = this.attachInternals(); // nur einmal pro Element erlaubt
  }
}

customElements.define('bewertungs-feld', BewertungsFeld);

3. setFormValue(): Den uebermittelten Wert festlegen

Die Methode internals.setFormValue(value) legt fest, welcher Wert beim Absenden des Formulars unter dem name-Attribut des Custom Elements in ein FormData-Objekt eingetragen wird. Der Parameter kann ein einfacher String, ein File-Objekt oder ein ganzes FormData-Objekt sein, was auch komplexere Steuerelemente wie einen Datei-Upload oder ein Mehrfachauswahl-Element erlaubt, ohne mehrere name-Attribute jonglieren zu muessen.

setFormValue() akzeptiert optional einen zweiten Parameter state, der vom uebermittelten Wert getrennt gespeichert wird und beim Formular-Reset ueber formStateRestoreCallback wiederhergestellt werden kann. Das ist etwa bei einem Steuerelement nuetzlich, dessen sichtbarer Anzeige-Zustand von der eigentlichen Datenrepraesentation abweicht, zum Beispiel bei einer Sternebewertung, die intern sowohl die Zahl als auch die Zwischenzustaende beim Hovern verwaltet.


set value(neuerWert) {
  this.#wert = neuerWert;
  // Wert wird beim Absenden des form-Elements uebermittelt
  this.#internals.setFormValue(String(neuerWert));
}

get value() {
  return this.#wert;
}

4. setValidity(): Eigene Validierungsregeln in die Constraint Validation API einklinken

internals.setValidity(flags, message, anchor) registriert das Custom Element bei der nativen Constraint Validation API des Browsers, mit denselben Zustands-Flags, die auch natives input verwendet, etwa valueMissing, tooShort oder eine eigene customError. Sobald ein Flag gesetzt ist, gilt das Element als ungueltig, form.reportValidity() zeigt automatisch die uebergebene message als Sprechblase an der uebergebenen anchor-Position an.

Ein Aufruf von internals.setValidity({}) ohne Flags markiert das Element wieder als gueltig. Weil diese Validierung Teil derselben API ist, die auch native Elemente nutzen, greifen CSS-Selektoren wie :invalid, :valid oder :user-invalid auf dem Custom Element genauso zuverlaessig wie auf einem regulaeren input-Element, ganz ohne manuelle Klassenzuweisung im JavaScript.


#validieren() {
  if (this.hasAttribute('required') && !this.#wert) {
    this.#internals.setValidity(
      { valueMissing: true },
      'Bitte eine Bewertung auswaehlen.',
      this.#stern1 // Anker-Element fuer die Sprechblase
    );
    return;
  }
  this.#internals.setValidity({}); // wieder gueltig
}

5. Eigene CSS-Zustaende mit internals.states und der :state()-Pseudoklasse

Ueber internals.states, ein CustomStateSet, kann ein Custom Element eigene, benutzerdefinierte Zustaende definieren, etwa checked oder pressed, die von aussen per CSS ueber die Pseudoklasse :state(checked) angesprochen werden koennen, ganz ohne dass dafuer eine class oder ein data-Attribut noetig waere.

Der Vorteil gegenueber einem klassischen CSS-Klassennamen ist die echte Kapselung: Ein per internals.states.add('checked') gesetzter Zustand ist von aussen ausschliesslich ueber die :state()-Selektor-Syntax lesbar, niemals ueber classList oder getAttribute, was verhindert, dass externer Code den internen Darstellungszustand versehentlich direkt manipuliert, statt die oeffentliche API des Elements zu nutzen.


// Zustand setzen, sobald der Nutzer eine Bewertung waehlt
this.#internals.states.add('checked');
this.#internals.states.delete('checked');

/* Passendes CSS ausserhalb der Komponente:
bewertungs-feld:state(checked) .stern { color: #ca8a04; }
*/

6. Barrierefreiheit: ARIA-Reflection ueber ElementInternals

ElementInternals stellt eine ganze Reihe von ARIA-Reflection-Eigenschaften bereit, etwa internals.role, internals.ariaLabel oder internals.ariaRequired, mit denen ein Custom Element seine semantische Rolle festlegen kann, ohne dass Entwickler, die das Element einsetzen, manuell passende aria-Attribute im Markup ergaenzen muessten.

Das ist besonders wertvoll, weil es die Verantwortung fuer korrekte Accessibility-Semantik dort verankert, wo sie hingehoert, naemlich in der Implementierung der Komponente selbst, statt sie jedem einzelnen Verwendungsort aufzuerlegen. Ein Screenreader erkennt das Element dadurch zuverlaessig als das, was es tatsaechlich ist, etwa als radiogroup oder als slider, unabhaengig vom gewaehlten HTML-Tag-Namen.

7. Reset, Disabled-Vererbung und weitere Formular-Callbacks

Neben setFormValue und setValidity definiert die formularassoziierte Custom-Element-Spezifikation eine Reihe von Lifecycle-Callbacks: formResetCallback wird aufgerufen, wenn das umschliessende Formular zurueckgesetzt wird, formDisabledCallback informiert das Element, wenn ein umschliessendes fieldset per disabled deaktiviert wird, und formStateRestoreCallback stellt bei Browser-Navigation gespeicherten Zustand wieder her.

Diese Callbacks sorgen dafuer, dass ein formularassoziiertes Custom Element sich in Randfaellen wie Formular-Reset oder verschachtelten disabled-fieldsets exakt so verhaelt wie ein natives Element, ohne dass die Komponente selbst auf Events wie reset am umschliessenden form lauschen muesste, was fehleranfaellig waere, sobald das Element aus dem DOM entfernt und neu eingefuegt wird.

8. Vollstaendiges Beispiel: eine formularfaehige Sternebewertung

Eine Sternebewertung eignet sich gut als durchgaengiges Beispiel, weil sie sowohl einen numerischen Wert uebermitteln als auch eine Pflichtfeld-Validierung unterstuetzen soll. Die Klasse kombiniert formAssociated, attachInternals im Konstruktor, setFormValue bei jeder Bewertungsaenderung und setValidity, um eine leere Bewertung als valueMissing zu markieren, sobald das required-Attribut gesetzt ist.

Zusaetzlich nutzt das Beispiel internals.states, um den Zustand hover waehrend des Mauszeigers ueber einem Stern zu setzen, sodass CSS allein per :state(hover) die Vorschau-Sterne einfaerben kann, ganz ohne zusaetzliche Klassenmanipulation im Event-Handler. Das Ergebnis verhaelt sich in jeder Hinsicht wie ein natives Formular-Steuerelement.


class SternBewertung extends HTMLElement {
  static formAssociated = true;
  #internals;
  #wert = 0;

  constructor() {
    super();
    this.#internals = this.attachInternals();
    this.attachShadow({ mode: 'open' });
  }

  setStern(zahl) {
    this.#wert = zahl;
    this.#internals.setFormValue(String(zahl));
    this.#pruefeValiditaet();
  }

  #pruefeValiditaet() {
    if (this.hasAttribute('required') && this.#wert === 0) {
      this.#internals.setValidity({ valueMissing: true }, 'Bitte bewerten.');
    } else {
      this.#internals.setValidity({});
    }
  }
}
customElements.define('stern-bewertung', SternBewertung);

9. Grenzen der API und Vergleich mit nativen Formular-Elementen

ElementInternals macht ein Custom Element formularfaehig, ersetzt aber nicht automatisch jede Tastatur-Interaktion, die native Elemente kostenlos mitbringen, etwa das Umschalten einer Checkbox per Leertaste. Solche Interaktionen muessen weiterhin explizit per Event-Listener implementiert werden, die API kuemmert sich ausschliesslich um die Formular-Anbindung, nicht um Tastatur-Semantik.

In aelteren Browsern ohne Unterstuetzung fuer ElementInternals faellt ein solches Custom Element vollstaendig aus der Formular-Uebermittlung heraus, eine Feature-Detection ueber 'attachInternals' in HTMLElement.prototype vor dem produktiven Einsatz ist deshalb empfehlenswert, mit einem versteckten nativen input als Fallback fuer den seltenen Fall fehlender Unterstuetzung.

Merkmal Natives input Custom Element + ElementInternals Custom Element ohne Internals
Eintrag in FormData Automatisch Automatisch ueber setFormValue Fehlt komplett
Native Validierung Eingebaut Ueber setValidity nachgebildet Nicht vorhanden
CSS-Pseudoklassen :invalid/:required Funktionieren Funktionieren ueber setValidity Funktionieren nicht
Aufnahme in form.elements Automatisch Automatisch nach attachInternals Fehlt komplett

Mironsoft

Moderne Browser-APIs, Performance und wartbares JavaScript

JavaScript, das im echten Browser robust bleibt, nicht nur im Tutorial?

Wir prüfen bestehenden Frontend-Code auf veraltete Patterns, unnötige Bibliotheken und Performance-Fallen und ersetzen sie durch moderne, native Browser-APIs, die weniger Bundle-Gewicht und weniger Wartungslast bedeuten.

Code-Review

Veraltete Patterns, unnötige Dependencies und Memory Leaks systematisch aufspüren.

Performance-Optimierung

Bundle-Größe, Ladezeit und Runtime-Performance mit modernen APIs verbessern.

Modernisierung

Native Browser-APIs statt schwerer Bibliotheken gezielt einführen.

10. Zusammenfassung

ElementInternals API: Das Wichtigste auf einen Blick

Kernidee

attachInternals() verbindet ein Custom Element direkt mit der nativen Formular-Infrastruktur des Browsers.

Wert uebermitteln

setFormValue() legt fest, was beim Absenden unter dem name-Attribut in FormData landet.

Validierung

setValidity() nutzt dieselben Flags wie native Elemente und aktiviert :invalid automatisch.

Wichtige Grenze

Tastatur-Interaktionen wie Leertaste fuer Checkboxen muessen weiterhin selbst implementiert werden.

11. FAQ: ElementInternals API: Das Wichtigste auf einen Blick

1Was bewirkt static formAssociated = true?
Es signalisiert dem Browser, dass das Custom Element an Formularen teilnehmen moechte, Voraussetzung dafuer, dass attachInternals ueberhaupt eine funktionsfaehige Formular-Anbindung liefert und das Element spaeter in form.elements auftaucht.
2Wie oft darf attachInternals aufgerufen werden?
Nur einmal pro Element, meist im Konstruktor. Ein zweiter Aufruf wirft einen Fehler, deshalb wird das zurueckgegebene ElementInternals-Objekt ueblicherweise einmalig in einer privaten Instanzvariable gespeichert und danach wiederverwendet.
3Was ist der Unterschied zwischen dem Wert- und dem State-Parameter von setFormValue?
Der erste Parameter ist der Wert, der beim Absenden tatsaechlich uebermittelt wird, der optionale zweite Parameter ist ein interner Zustand, der beim Browser-Zuruecksetzen ueber formStateRestoreCallback wiederhergestellt werden kann und vom uebermittelten Wert abweichen darf.
4Muss ich setValidity bei jeder Wertaenderung neu aufrufen?
Ja, Validitaet ist kein einmalig gesetzter Zustand, sondern muss bei jeder relevanten Aenderung neu bewertet und ueber setValidity aktualisiert werden, sonst bleibt ein Element faelschlich als gueltig oder ungueltig markiert.
5Wozu dient internals.states?
internals.states ist ein CustomStateSet, mit dem ein Element eigene benutzerdefinierte Zustaende wie checked oder pressed definiert, die von aussen ausschliesslich ueber die CSS-Pseudoklasse :state() gelesen werden koennen, ohne class oder data-Attribute zu benoetigen.
6Kann ich mit ElementInternals auch Accessibility-Eigenschaften setzen?
Ja, ueber Eigenschaften wie internals.role oder internals.ariaLabel lassen sich ARIA-Semantiken direkt in der Komponentenimplementierung festlegen, sodass Nutzer der Komponente keine manuellen aria-Attribute mehr ergaenzen muessen.
7Reagiert mein Custom Element automatisch auf ein Formular-Reset?
Nur, wenn formResetCallback implementiert wird. Der Browser ruft diesen Callback bei einem Formular-Reset automatisch auf, die eigentliche Ruecksetzlogik, etwa das Wiederherstellen eines Default-Werts, muss innerhalb des Callbacks selbst geschrieben werden.
8Was passiert, wenn ein umschliessendes fieldset deaktiviert wird?
Der Browser ruft formDisabledCallback mit einem boolean-Parameter auf, das Custom Element muss darauf selbst reagieren, etwa indem es Interaktionen sperrt und sich optisch als deaktiviert darstellt, das geschieht nicht automatisch ohne diesen Callback.
9Brauche ich noch ein verstecktes input-Element als Fallback?
In modernen Umgebungen nicht mehr, in Projekten mit Unterstuetzung sehr alter Browser ist ein per Feature-Detection aktivierter Fallback mit verstecktem input trotzdem eine sinnvolle Absicherung, damit die Formularfunktion nicht komplett ausfaellt.
10Ersetzt ElementInternals auch Tastatur-Interaktionen wie das Umschalten per Leertaste?
Nein, die API kuemmert sich ausschliesslich um die Anbindung an die Formular-Infrastruktur und Validierung, saemtliche Tastatur- und Zeigegeraete-Interaktionen muessen weiterhin explizit per eigenen Event-Listenern implementiert werden.