The form-associated custom elements APIs enable custom elements to participate in form submission and validation lifecycles.
- Specification
- More capable form controls
- Creating Custom Form Controls with ElementInternals
- Creating a form-associated custom element
- Polyfill
- Open WC FormControl
- Chrome/Edge (shipped in version 77)
- Firefox (shipped in version 90)
- Safari has merged a PR implementing the first part of the
ElementInternals
spec which includes the form-associated custom elements behavior; however, the initial PR doesn't include the actual form-association APIs. The polyfill does check for the presence of the form-associated APIs separately fromElementInternals
, so this is not likely to cause any issues if it is released in part.
The form-associated custom elements APIs are implemented within the attachInternals
method on the HTMLElement
prototype. Calling attachInternals
returns an instance of an ElementInternals
object which grants developers the ability to interact with form elements provided they designate their element as a form-associated element.
<form>
<fancy-input name="fancy"></fancy-input>
</form>
<script>
class FancyInput extends HTMLElement {
static get formAssociated() {
return true;
}
constructor() {
super();
this.#internals = this.attachInternals();
this.#internals.setFormValue('I can participate in a form!');
}
}
customElements.define('fancy-input', FancyInput);
document.querySelector('form').addEventListener('submit', event => {
event.preventDefault();
const formData = new FormData(event.target);
console.log(formData.get('fancy')); // logs 'I can participate in a form!'
});
</script>
The setFormValue
method can accept several types of data including strings, FileData and FormData objects, the latter of which can allow a nested form to participate with a parent in its entirety.
In addition to providing an method for adding a value to a form object, the form-associated APIs provide a surface to allow custom elements to participate in form validation.
class FancyInput extends HTMLElement {
static get formAssociated() {
return true;
}
constructor() {
super();
const root = this.attachShadow({ mode: 'open' });
this.#internals = this.attachInternals();
this.#internals.setFormValue('I can participate in a form!');
const button = document.createElement('button');
root.append(button);
button.addEventListener('click', this.#onClick);
this.button = button;
}
#onClick = () => {
if (this.#internals.invalid) {
this.#internals.setValidity(); // Marks the element as valid
} else {
this.#internals.setValidity({
customError: true
}, 'You must click the button', this.button); // Marks the element as invalid and will focus on the button when the form checks validity
}
}
- Enable custom elements to participate in form submission by adding a value to the
FormData
object, adding to theHTMLFormElement.prototype.elements
object and in other submission formats. - Enables custom elements to participate in the form validation lifecycle by providing APIs similar to and building on top of the constraint validation APIs.
- HTMLFormElement elements does not contain form-associated CustomElements when name attribute is shared
- The form-associated APIs currently have no way for a developer to behave as a custom submit button. Though there are several workarounds.
- CustomStateSet
- AriaMixin/accessibility object model
- It is currently unclear how form-associated custom elements should participate in the autocomplete lifecycle despite there being an API for that purpose. It is currently unclear per the spec how this behavior should work. Currently Chromium implements code for the
formStateRestoreCallback
callback only for restore events (such as back button or page refresh), but does not call the custom element reaction for autocomplete. Firefox currently does not ship any code referencing theformStateRestoreCallback
.