Signatur
Beschreibung
attachInternals() ist der Einstiegspunkt, über den ein autonomes Custom Element (also eine Klasse, die direkt von HTMLElement erbt) Zugriff auf Browser-Funktionen erhält, die sonst nur eingebauten Elementen vorbehalten sind. Das zurückgegebene ElementInternals-Objekt bietet unter anderem setFormValue(), setValidity(), checkValidity(), reportValidity(), form, labels, states (benutzerdefinierte Pseudoklassen wie :state(checked)) sowie ARIA-Eigenschaften wie role und ariaLabel.
Damit ein Element als formularzugehöriges Custom Element gilt (Wert wird mit dem Formular übermittelt, <label> funktioniert, disabled/reset-Callbacks werden aufgerufen), muss die Klasse zusätzlich die statische Eigenschaft static formAssociated = true besitzen. Ohne dieses Flag funktionieren zwar die ARIA-Eigenschaften und states, die formularbezogenen Methoden werfen aber einen NotSupportedError.
Die Methode wird typischerweise einmalig im Konstruktor aufgerufen; das Ergebnis speichert man in einem privaten Feld (z. B. #internals), damit Außenstehende die Formular- oder Barrierefreiheits-Semantik nicht manipulieren können.
Rückgabewert
ElementInternals-Objekt, das fest an das aufrufende Element gebunden ist.Beispiele
Formularzugehöriges Eingabeelement mit Validierung
class RatingInput extends HTMLElement {
static formAssociated = true;
#internals;
#value = 0;
constructor() {
super();
this.#internals = this.attachInternals();
this.attachShadow({ mode: 'open' }).innerHTML =
'<button type="button">★ vergeben</button>';
this.shadowRoot.querySelector('button')
.addEventListener('click', () => { this.value = this.#value % 5 + 1; });
this.value = 0;
}
get value() { return this.#value; }
set value(v) {
this.#value = Number(v);
this.#internals.setFormValue(String(this.#value));
if (this.#value === 0) {
this.#internals.setValidity({ valueMissing: true }, 'Bitte eine Bewertung wählen.');
} else {
this.#internals.setValidity({});
}
}
get form() { return this.#internals.form; }
checkValidity() { return this.#internals.checkValidity(); }
// Wird beim Zurücksetzen des Formulars aufgerufen
formResetCallback() { this.value = 0; }
}
customElements.define('rating-input', RatingInput);
// HTML: <form><rating-input name="sterne"></rating-input></form>
const form = document.querySelector('form');
const rating = form.querySelector('rating-input');
console.log(rating.checkValidity());
rating.value = 4;
console.log(new FormData(form).get('sterne'));
ARIA-Rolle und eigener Zustand ohne Formularbezug
class ToggleSwitch extends HTMLElement {
#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('on');
on ? this.#internals.states.delete('on') : this.#internals.states.add('on');
this.#internals.ariaChecked = String(!on);
}
}
customElements.define('toggle-switch', ToggleSwitch);
// CSS: toggle-switch:state(on) { background: green; }
const sw = document.createElement('toggle-switch');
document.body.append(sw);
sw.click();
console.log(sw.matches(':state(on)'));
Fehlerfälle erkennen
class MyEl extends HTMLElement {}
customElements.define('my-el', MyEl);
const el = new MyEl();
const internals = el.attachInternals();
try {
el.attachInternals(); // zweiter Aufruf
} catch (e) {
console.log(e.name);
}
try {
internals.setFormValue('x'); // formAssociated fehlt
} catch (e) {
console.log(e.name);
}
try {
document.createElement('div').attachInternals(); // kein Custom Element
} catch (e) {
console.log(e.name);
}
// Wichtig · Fallstricke
- Nur einmal pro Element: Ein zweiter Aufruf wirft einen
NotSupportedError(DOMException). Das Objekt daher sofort speichern. - Nur für autonome Custom Elements: Bei eingebauten Elementen (
div), bei angepassten eingebauten Elementen (is="…"), bei noch nicht definierten Elementen oder wenn die Klassestatic disabledFeatures = ['internals']setzt, wird ebenfallsNotSupportedErrorgeworfen. formAssociatednicht vergessen: Ohnestatic formAssociated = truewerfensetFormValue(),setValidity(),form,labelsusw. Fehler.- Kapselung: Das Objekt nicht öffentlich exponieren (privates Feld
#internalsnutzen), sonst kann fremder Code Validität und Formularwert manipulieren. - Kompatibilität: Seit Safari 16.4 in allen modernen Browsern verfügbar.
statesund die CSS-Syntax:state()sind neuer (Chrome 125, Firefox 126, Safari 17.4); ältere Chrome-Versionen nutzten:--name. Für ältere Umgebungen gibt es das Polyfill element-internals-polyfill. Per Feature-Erkennung prüfen:'attachInternals' in HTMLElement.prototype.