Signatur
Beschreibung
setValidity() ist das Gegenstück zu setCustomValidity() nativer Eingabefelder, aber für formularassoziierte Custom Elements, also Klassen mit static formAssociated = true, die über this.attachInternals() ein ElementInternals-Objekt erhalten. Damit nimmt ein eigenes Steuerelement vollständig an der Constraint Validation API teil: form.checkValidity(), reportValidity(), die CSS-Pseudoklassen :invalid/:valid und das Blockieren des Absendens funktionieren wie bei einem <input>.
Das erste Argument ist ein Objekt mit Flags, die denen von ValidityState entsprechen (valueMissing, typeMismatch, patternMismatch, tooLong, tooShort, rangeUnderflow, rangeOverflow, stepMismatch, badInput, customError). Ist mindestens ein Flag true, gilt das Element als ungültig, und dann muss eine nicht leere Meldung übergeben werden. Ein leeres Objekt {} setzt das Element wieder auf gültig.
Mit dem optionalen anchor gibst du ein Element aus dem Shadow DOM an, auf das der Browser bei reportValidity() den Fokus setzt und an dem er die Fehler-Blase anzeigt, zum Beispiel das interne <input>. Normalerweise rufst du setValidity() bei jeder Wertänderung neu auf, direkt nach setFormValue().
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $flags | Object | {} | Objekt mit booleschen Gültigkeits-Flags (valueMissing, typeMismatch, patternMismatch, tooLong, tooShort, rangeUnderflow, rangeOverflow, stepMismatch, badInput, customError). Ein leeres Objekt oder ein Aufruf ohne Argumente markiert das Element als gültig. |
| $message | string | Fehlermeldung, die in validationMessage landet und in der Fehler-Blase des Browsers erscheint. Pflicht, sobald ein Flag true ist. |
|
| $anchor | HTMLElement | Element im Shadow DOM des Custom Elements, das bei reportValidity() den Fokus und die Fehleranzeige bekommt. Es muss ein Nachfahre im Shadow Tree sein. |
Beispiele
Pflichtfeld-Validierung in einem eigenen Eingabe-Element
class RatingInput extends HTMLElement {
static formAssociated = true;
#internals = this.attachInternals();
#value = 0;
connectedCallback() {
this.#update();
}
set value(v) {
this.#value = Number(v);
this.#update();
}
get value() {
return this.#value;
}
#update() {
this.#internals.setFormValue(String(this.#value));
if (this.hasAttribute('required') && this.#value === 0) {
this.#internals.setValidity(
{ valueMissing: true },
'Bitte eine Bewertung von 1 bis 5 Sternen abgeben.'
);
} else {
this.#internals.setValidity({}); // gültig
}
}
checkValidity() { return this.#internals.checkValidity(); }
get validationMessage() { return this.#internals.validationMessage; }
}
customElements.define('rating-input', RatingInput);
const el = document.createElement('rating-input');
el.setAttribute('required', '');
document.body.append(el);
console.log(el.checkValidity(), el.validationMessage);
el.value = 4;
console.log(el.checkValidity());
Validierung am internen Input verankern (anchor)
class ZipField extends HTMLElement {
static formAssociated = true;
constructor() {
super();
this.internals = this.attachInternals();
const root = this.attachShadow({ mode: 'open' });
root.innerHTML = '<input part="input" inputmode="numeric">';
this.input = root.querySelector('input');
this.input.addEventListener('input', () => this.#validate());
}
connectedCallback() {
this.#validate();
}
#validate() {
const val = this.input.value;
this.internals.setFormValue(val);
if (!/^\d{5}$/.test(val)) {
this.internals.setValidity(
{ patternMismatch: true },
'Die Postleitzahl muss aus genau 5 Ziffern bestehen.',
this.input // Fokus und Fehler-Blase am inneren Input
);
} else {
this.internals.setValidity({});
}
}
}
customElements.define('zip-field', ZipField);
// <form><zip-field name="plz"></zip-field><button>Senden</button></form>
// Beim Absenden blockiert der Browser und fokussiert das innere <input>.
Asynchrone Serverprüfung mit customError
const checkUsername = async (internals, input) => {
const name = input.value.trim();
internals.setFormValue(name);
const res = await fetch(`/api/users/exists?name=${encodeURIComponent(name)}`);
const { exists } = await res.json();
if (exists) {
internals.setValidity(
{ customError: true },
`Der Benutzername „${name}“ ist bereits vergeben.`,
input
);
} else {
internals.setValidity({});
}
console.log(internals.validity.customError, internals.validity.valid);
};
// Wichtig · Fallstricke
- Nur für formularassoziierte Elemente: Ohne
static formAssociated = truewirft der Aufruf eineNotSupportedError-DOMException. - Ist mindestens ein Flag
trueund fehltmessageoder ist sie leer, wird einTypeErrorgeworfen. - Ist
anchorkein Nachfahre im Shadow Tree des Elements, wird eineNotFoundError-DOMExceptiongeworfen. - Die Flags werden bei jedem Aufruf komplett ersetzt, nicht zusammengeführt. Wer mehrere Fehler gleichzeitig melden will, übergibt alle Flags in einem Aufruf.
- Der Gültigkeitszustand ändert sich nicht von selbst. Du musst
setValidity()nach jeder Wertänderung, beiformResetCallback()und bei Änderungen relevanter Attribute (z. B.required) erneut aufrufen. - Die Meldung wird als Klartext angezeigt, nicht als HTML. Ein XSS-Risiko besteht dadurch nicht. Benutzereingaben in der Meldung solltest du trotzdem kürzen.
- Elemente mit dem Attribut
disabledoder innerhalb eines deaktivierten<fieldset>werden von der Validierung ausgenommen (willValidateist dannfalse). - Browser-Unterstützung: Chrome/Edge 77+, Firefox 98+, Safari erst ab 16.4. Für ältere Safari-Versionen gibt es das Polyfill
element-internals-polyfill.