# Build Custom Forms in Native Shadow DOM
Input elements inside a form that uses native shadow are hidden inside the shadow DOM. The form can't validate the input elements for submission, so their data isn't included in the FormData object. Furthermore, form submission fails because the submit event can't escape the shadow boundary.
To include components or elements using native shadow during form submission, use the ElementInternals Web API and the related Form-Associated Custom Elements (FACE) lifecycle API. LWC support for ElementInternals Web API and FACE is
available in LWC v6.0.0 and later.
Using ElementInternals, you can manage a Lightning web component's internal states, such as default Accessible Rich Internet Applications (ARIA) role or ARIA label, and have your component participate in form submissions and validations.
Note
ElementInternals applies to light DOM and native shadow only. Synthetic shadow supports form association without the use of ElementInternals. However, components in synthetic shadow must add ARIA attributes and roles explicitly.
ElementInternals and the FACE lifecycle API are not supported on the Salesforce Platform.
ElementInternals and the FACE lifecycle API provides several key functionalities.
- Default ARIA semantics-A Lightning web component can have default ARIA attributes and roles even without an explicit ARIA attribute on the host.
- Form association-A Lightning web component can communicate its form-based states and use lifecycle callbacks corresponding to the form. Call
this.attachInternalsto associate anElementInternalsobject to your component.
Note
Use ElementInternals with components that extends LightningElement only.
# Use the ElementInternals Web API
ElementInternals allows Lightning web components to fully participate in HTML forms. You can assign the internals to a property and define your ARIA role and attribute values.
export default class extends LightningElement {
constructor() {
super();
const internals = this.attachInternals();
internals.role = "button";
internals.ariaLabel = "My button";
}
}
this.attachInternals() is available at any point in the component lifecycle, similar to this.template. However, you can't access the API from outside a component.
const component = document.querySelector("my-component");
console.log(component.attachInternals); // returns undefined
You can only call this.attachInternals() once. If you call it more than once, it throws an error. Avoid calling this.attachInternals() in a lifecycle hook that can be called multiple times, such as connectedCallback or renderedCallback.
# Use the Form Association Lifecycle Callbacks
To use the lifecycle callbacks, add a formAssociated property to identify the element as a form control. These callbacks are optional and lets your element perform something at that point in the lifecycle.
// control.js
import { LightningElement, api } from "lwc";
export default class extends LightningElement {
static formAssociated = true;
// provides access to methods and properties for form controls
@api internals = this.attachInternals();
// Comments in the lifecycle callbacks are taken from:
// https://webkit.org/blog/13711/elementinternals-and-form-associated-custom-elements/
formAssociatedCallback() {
// Called when the associated form element changes to form
}
formDisabledCallback() {
// Called when the disabled state of the element changes
}
formResetCallback() {
// Called when the form is being reset. (e.g. user pressed input[type=reset] button).
// Custom element should clear whatever value set by the user.
}
formStateRestoreCallback(state, mode) {
// Called when the browser is trying to restore element’s state to state in which case reason is “restore”, or when the browser is trying to fulfill autofill on behalf of user in which case reason is “autocomplete”. In the case of “restore”, state is a string, File, or FormData object previously set as the second argument to setFormValue.
}
}
For more information, see web.dev: Form-associated custom elements lifecycle callbacks
To use the component that defines the lifecycle callbacks, nest it within a <form> tag.
<!-- app.js -->
<template>
<form>
<x-control></x-control>
</form>
</template>
Similar to the behavior of the LWC connectedCallback lifecycle hook, you can't access formAssociated or the FACE lifecycle callbacks outside a component.
const component = document.querySelector("my-component");
console.log(component.formAssociatedCallback); // returns undefined
console.log(component.constructor.formAssociated); // returns undefined
# Usage Considerations
ElementInternalsis available only with Chromium, Firefox, and Safari.this.attachInternals()throws an error in unsupported browsers. LWC doesn’t provide a polyfill for unsupported browsers.this.attachInternalsis undefined in server-side rendering.ElementInternalsand the FACE lifecycle callbacks aren't currently serializable as HTML.internals.shadowRootdoesn't return an instance of theShadowRoot; it returns the same proxy object asthis.template. For example,this.template === internals.shadowRootreturnstrue.- Register a component with the
formAssociatedproperty once only. To register multiple components with differentformAssociatedvalues, use a different tag name. For example, you can't use two components with thex-my-componenttag name, one withformAssociated = trueand the other withformAssociated = false.
# Example: Create a Custom Form with Validity Checks
This example creates a custom form using the <input> element and implements the validity states for them. The form component contains multiple instances of the control component, which uses an <input> element with click and change event handlers. The <form> tag embeds several buttons, which either resets the form, toggles the disabled property on the input fields, or submits the form.
When you click the required input fields, checkValidity() displays a custom error if nothing is entered. The CSS appends a red border when the field is in an invalid state. Resetting the form removes the error state on the input fields.
See Also
- MDN web docs: ElementInternals
- web.dev: Form-associated custom elements
- HTML Specification: Custom elements