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>:

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.