ElementInternals API for Accessible Form Components
The ElementInternals API is a modern web standard that
allows custom Web Components to seamlessly participate in native HTML
forms and manage internal accessibility semantics. By bridging the gap
between autonomous custom elements and built-in browser form controls,
developers can build encapsulated JavaScript components that support
native form validation, submit values alongside standard inputs, and
expose proper ARIA attributes to assistive technologies without
modifying host element attributes.
What is the ElementInternals API?
Traditionally, custom elements created via the Web Components
standard could not directly participate in native HTML
<form> submissions, lifecycle events, or standard
validation mechanisms without using hidden <input>
elements or complex JavaScript workarounds.
The ElementInternals interface solves this by providing
a programmatic gateway into the browser’s internal control machinery. It
is instantiated inside a custom element’s constructor by calling
this.attachInternals(). This returns an
ElementInternals object that grants access to form
integration, validation states, and accessibility object model (AOM)
properties.
class CustomCheckbox extends HTMLElement {
static formAssociated = true;
constructor() {
super();
this.internals = this.attachInternals();
this.attachShadow({ mode: 'open' });
}
}
customElements.define('custom-checkbox', CustomCheckbox);Attaching Custom Elements to HTML Forms
To allow a JavaScript component to function as a form control, the
custom element class must declare the static property
static formAssociated = true. Once enabled, the
ElementInternals instance provides methods to synchronize
the component’s internal state with the containing
<form>:
setFormValue(value): Sets the value submitted with the form. Passingnullremoves the component from form submission data.- Form Lifecycles: The component can implement native
lifecycle callbacks, including
formAssociatedCallback(form),formDisabledCallback(disabled),formResetCallback(), andformStateRestoreCallback(state, mode).
When the parent form is submitted, serialized, or reset via standard
methods like FormData(form) or native submit buttons, the
custom component behaves identically to standard
<input>, <select>, or
<textarea> elements.
Native Form Validation Integration
ElementInternals grants access to the native Constraint
Validation API. This allows developers to mark custom components as
invalid, set custom error messages, and trigger browser-native
validation tooltips without relying on external validation
libraries.
Key methods and properties include: -
setValidity(flags, message, anchor):
Updates the element’s validity state. Flags can include
valueMissing, typeMismatch,
patternMismatch, tooLong,
tooShort, rangeUnderflow,
rangeOverflow, stepMismatch,
badInput, or customError. -
validity: Returns a read-only
ValidityState object. -
validationMessage: Returns the current
localized error message. - checkValidity() and
reportValidity(): Evaluates validity and prompts
the user agent to display default validation UI if invalid.
Managing Accessibility and ARIA Semantics
Before ElementInternals, assigning accessibility roles
and states required modifying the host element’s attributes directly
(e.g., this.setAttribute('role', 'checkbox')), which
cluttered the DOM and allowed external scripts to inadvertently
overwrite critical accessibility metadata.
With ElementInternals, accessibility is managed via
internal ARIA reflection properties directly on the internals
instance:
this.internals.role = 'checkbox';
this.internals.ariaChecked = 'true';
this.internals.ariaDisabled = 'false';
this.internals.ariaRequired = 'true';Assistive technologies, such as screen readers, interpret these internally assigned ARIA states directly from the browser’s accessibility tree. This ensures default accessibility behaviors remain fully functional and encapsulated, while still allowing consumers of the component to override attributes externally if explicitly necessary.