Signatur
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,FormDataodernull).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– einCustomStateSetfü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
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)));
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)'));
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 = trueist Pflicht. Fehlt die Eigenschaft, werfen Formularmethoden wiesetFormValue()einenNotSupportedError.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
ElementInternalssind am Element selbst nicht sichtbar. Öffentliche Getter wievalue,form,validityodercheckValidity()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 einenTypeError. MitsetValidity({})wird das Element wieder gültig. - Über
ElementInternalsgesetzte ARIA-Werte sind nur Standardwerte. Vom Anwender gesetzterole- oderaria-*-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 Polyfillelement-internals-polyfill.