Start · Sprachen · JavaScript · Referenz · ElementInternals

ElementInternals

Schnittstelle

Über <code>ElementInternals</code> nehmen benutzerdefinierte Elemente vollständig an HTML-Formularen teil: Formularwert, Validierung, Formularstatus und ARIA-Semantik.

seit JavaScript Baseline 2023: Chrome 77+, Firefox 93+, Safari 16. Kategorie: webapi

Signatur

interface ElementInternals

Beschreibung

ElementInternals verbindet ein formularassoziiertes benutzerdefiniertes Element (Custom Element) mit dem Formular-Mechanismus des Browsers. Ein Objekt dieser Art bekommt man nur über this.attachInternals() im Konstruktor oder in einer Methode der Element-Klasse. Das Element muss dafür die statische Eigenschaft static formAssociated = true deklarieren. Erst dann wird es wie ein natives <input> behandelt: Es landet in FormData, reagiert auf form.reset(), kann über <fieldset disabled> deaktiviert werden und macht bei der Formularvalidierung mit.

Die wichtigsten Mitglieder:

  • setFormValue(value, state?) – legt den Wert fest, der beim Absenden übertragen wird (string, File, FormData oder null).
  • setValidity(flags, message?, anchor?), checkValidity(), reportValidity(), validity, validationMessage, willValidate – Constraint-Validierung wie bei nativen Feldern.
  • form, labels – das zugehörige <form> und die verknüpften <label>-Elemente.
  • states – ein CustomStateSet für eigene Pseudoklassen wie :state(checked).
  • shadowRoot – Zugriff auf den Shadow Root, auch auf einen geschlossenen oder deklarativ erzeugten.
  • ARIA-Eigenschaften (role, ariaLabel, ariaChecked …) – Standardsemantik festlegen, ohne sichtbare Attribute im DOM zu setzen.

Außerdem kann die Klasse Lebenszyklus-Callbacks für Formulare definieren: formAssociatedCallback(form), formDisabledCallback(disabled), formResetCallback() und formStateRestoreCallback(state, mode). Damit reagiert das Element auf Zurücksetzen, Deaktivieren und die automatische Wiederherstellung von Formularen durch den Browser.

Parameter

Name Typ Default Beschreibung
$value (setFormValue) Pflicht string | File | FormData | null Wert, der beim Absenden übermittelt wird. Bei FormData werden mehrere Einträge übertragen, bei null wird nichts übertragen.
$state (setFormValue) string | File | FormData | null value Interner Zustand, den der Browser bei der Wiederherstellung an formStateRestoreCallback übergibt.
$flags (setValidity) Pflicht ValidityStateFlags Objekt wie { valueMissing: true }. Ein leeres Objekt {} markiert das Element als gültig.
$message (setValidity) string Fehlermeldung. Ist sie Pflicht, sobald ein Flag true ist.
$anchor (setValidity) HTMLElement Element im Shadow DOM, an dem der Browser die Fehlermeldung anzeigt und auf das er den Fokus setzt.

Rückgabewert

Typ
ElementInternals
Beschreibung
attachInternals() liefert eine Instanz von ElementInternals, die fest an das benutzerdefinierte Element gebunden ist.

Beispiele

Formularassoziiertes Eingabefeld mit Validierung

class RatingInput extends HTMLElement {
  static formAssociated = true;
  #internals;
  #value = 0;

  constructor() {
    super();
    this.#internals = this.attachInternals();
    const root = this.attachShadow({ mode: 'open' });
    root.innerHTML = '<button type="button">★ vergeben</button>';
    root.querySelector('button').addEventListener('click', () => {
      this.value = (this.#value % 5) + 1;
    });
    this.#update();
  }

  get value() { return this.#value; }
  set value(v) {
    this.#value = Number(v);
    this.#update();
  }

  #update() {
    this.#internals.setFormValue(this.#value ? String(this.#value) : null);
    if (this.hasAttribute('required') && !this.#value) {
      this.#internals.setValidity(
        { valueMissing: true },
        'Bitte eine Bewertung abgeben.',
        this.shadowRoot.querySelector('button')
      );
    } else {
      this.#internals.setValidity({});
    }
  }

  get form() { return this.#internals.form; }
  get validity() { return this.#internals.validity; }
  checkValidity() { return this.#internals.checkValidity(); }

  formResetCallback() { this.value = 0; }
}

customElements.define('rating-input', RatingInput);

// HTML: <form id="f"><rating-input name="sterne" required></rating-input></form>
const form = document.querySelector('#f');
const rating = form.querySelector('rating-input');
console.log(rating.checkValidity());
rating.value = 4;
console.log(Object.fromEntries(new FormData(form)));
false { sterne: "4" }

Eigene Zustände mit CustomStateSet und :state()

class ToggleSwitch extends HTMLElement {
  static formAssociated = true;
  #internals = this.attachInternals();

  connectedCallback() {
    this.#internals.role = 'switch';
    this.#internals.ariaChecked = 'false';
    this.tabIndex = 0;
    this.addEventListener('click', () => this.toggle());
  }

  toggle() {
    const on = !this.#internals.states.has('checked');
    on ? this.#internals.states.add('checked') : this.#internals.states.delete('checked');
    this.#internals.ariaChecked = String(on);
    this.#internals.setFormValue(on ? 'on' : null);
  }
}

customElements.define('toggle-switch', ToggleSwitch);

// CSS: toggle-switch:state(checked) { background: green; }
const sw = document.createElement('toggle-switch');
document.body.append(sw);
sw.click();
console.log(sw.matches(':state(checked)'));
true

Deaktivieren und Wiederherstellen des Formularstatus

class DateField extends HTMLElement {
  static formAssociated = true;
  #internals = this.attachInternals();

  formDisabledCallback(disabled) {
    // wird z. B. von <fieldset disabled> ausgelöst
    this.toggleAttribute('aria-disabled', disabled);
    console.log('deaktiviert:', disabled);
  }

  formStateRestoreCallback(state, mode) {
    // mode: 'restore' (Zurück-Navigation) oder 'autocomplete'
    console.log('Wiederherstellung', mode, state);
    this.#internals.setFormValue(state);
  }

  formAssociatedCallback(form) {
    console.log('zugeordnet zu', form?.id ?? 'keinem Formular');
  }
}

customElements.define('date-field', DateField);

// Wichtig · Fallstricke

  • static formAssociated = true ist Pflicht. Fehlt die Eigenschaft, werfen Formularmethoden wie setFormValue() einen NotSupportedError.
  • attachInternals() darf pro Element nur einmal aufgerufen werden, ein zweiter Aufruf wirft einen Fehler. Für eingebaute Erweiterungen (is="…") steht die Methode nicht zur Verfügung.
  • Die Eigenschaften und Methoden von ElementInternals sind am Element selbst nicht sichtbar. Öffentliche Getter wie value, form, validity oder checkValidity() muss man selbst bereitstellen. Das Objekt in einem privaten Feld (#internals) ablegen, damit fremder Code keinen Zugriff hat.
  • Wer setValidity() mit einem Fehler-Flag aufruft, muss auch eine Meldung übergeben, sonst gibt es einen TypeError. Mit setValidity({}) wird das Element wieder gültig.
  • Über ElementInternals gesetzte ARIA-Werte sind nur Standardwerte. Vom Anwender gesetzte role- oder aria-*-Attribute haben Vorrang.
  • Browser-Unterstützung: Chrome/Edge seit Version 77 (ARIA-Teil später), Firefox seit 93, Safari erst seit 16.4. Die Pseudoklasse :state() ist neuer; ältere Chromium-Versionen kannten nur die Syntax :--name. Für ältere Safari-Versionen gibt es das Polyfill element-internals-polyfill.