HTML - HTML Form-Associated Custom Elements
1. Introduction
HTML Form-Associated Custom Elements are a modern HTML feature that allows developers to create their own custom HTML elements and make them behave like standard form controls such as <input>, <select>, and <textarea>.
In traditional HTML, forms use predefined elements to collect information from users. For example, a registration form may contain input fields for a name, email address, and password. However, when a website needs a specialized control, such as a custom date selector, a rating component, or a reusable address field, the built-in elements may not provide all the required functionality.
Developers can create custom elements using JavaScript and Web Components. By using the ElementInternals API and declaring a custom element as form-associated, developers can integrate these elements with standard HTML forms.
This feature allows custom elements to participate in form submission, validation, and form state restoration while maintaining a reusable and consistent interface.
2. What Are Form-Associated Custom Elements?
A Form-Associated Custom Element (FACE) is a custom HTML element that can participate in the browser's built-in form system.
Normally, a custom element is treated as an independent HTML component. It does not automatically behave like a standard form control. For example, a custom element may display a value on the screen, but that value will not necessarily be included when the user submits a form.
Form-associated custom elements solve this problem by allowing developers to connect their custom components to the browser's form-handling mechanisms.
Consider a custom element named <custom-email>:
<form action="/register" method="post">
<label for="email">Email Address:</label>
<custom-email
id="email"
name="email">
</custom-email>
<button type="submit">Register</button>
</form>
In this example, <custom-email> represents a custom form control. For it to work as a genuine form-associated element, its JavaScript class must implement the required behavior, including form association and value management.
Once implemented correctly, the custom element can contribute its value to form submission, participate in validation, and integrate with other standard form features.
3. How to Create a Form-Associated Custom Element
Creating a form-associated custom element involves several steps. Developers define a custom HTML element, enable form association, establish its value, and connect it to the browser's form system.
Step 1: Define a custom element
Custom elements are created by extending the JavaScript HTMLElement class.
For example:
class CustomEmail extends HTMLElement {
constructor() {
super();
}
}
customElements.define("custom-email", CustomEmail);
Here:
-
HTMLElementis the base class for HTML elements. -
CustomEmaildefines the behavior of the new component. -
customElements.define()registers the custom element with the browser. -
<custom-email>becomes a recognized element that developers can use in HTML.
However, this basic implementation does not yet make the element participate in form submission.
Step 2: Enable form association
To make the custom element form-associated, developers use the static formAssociated property.
class CustomEmail extends HTMLElement {
static formAssociated = true;
constructor() {
super();
this.internals = this.attachInternals();
}
}
customElements.define("custom-email", CustomEmail);
The statement static formAssociated = true tells the browser that the custom element is designed to participate in forms.
The method attachInternals() returns an ElementInternals object. This object provides access to capabilities that allow the custom element to interact with the browser's form system.
For example, it enables the component to set its submitted value, manage validation states, and associate itself with a form.
attachInternals() is generally called once during construction. It is available to custom elements that meet the browser's requirements for using this API.
Step 3: Set the form value
The custom element must provide a value that the browser can submit with the form.
Developers use the setFormValue() method on the ElementInternals object.
For example:
this.internals.setFormValue("[email protected]");
This statement sets the value that the browser will submit for the custom element.
If the element has a name="email" attribute, the submitted form data can contain the following entry:
email=student%40example.com
The browser encodes the submitted data according to the form submission process. The example above illustrates URL-encoded data, not the literal format used by every submission method.
If the component's value changes, the developer must update the form value accordingly.
4. Complete Example of a Form-Associated Custom Element
The following example demonstrates a working custom text input that participates in form submission and basic required-field validation.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form-Associated Custom Element</title>
</head>
<body>
<h2>Student Registration</h2>
<form id="studentForm">
<label for="studentName">Student Name:</label>
<custom-text
id="studentName"
name="studentName"
required>
</custom-text>
<button type="submit">Submit</button>
</form>
<script>
class CustomText extends HTMLElement {
static formAssociated = true;
static get observedAttributes() {
return ["required", "disabled"];
}
constructor() {
super();
this.internals = this.attachInternals();
this.input = document.createElement("input");
this.input.type = "text";
this.input.addEventListener("input", () => {
this.updateValue();
});
}
connectedCallback() {
if (!this.input.isConnected) {
this.appendChild(this.input);
}
this.syncAttributes();
this.updateValue();
}
attributeChangedCallback() {
this.syncAttributes();
if (this.input) {
this.updateValue();
}
}
syncAttributes() {
this.input.required = this.hasAttribute("required");
this.input.disabled = this.hasAttribute("disabled");
}
updateValue() {
const value = this.input.value;
this.internals.setFormValue(
this.input.disabled ? null : value
);
if (this.input.required && value.trim() === "") {
this.internals.setValidity(
{ valueMissing: true },
"Please enter the student name.",
this.input
);
} else {
this.internals.setValidity({});
}
}
formResetCallback() {
this.input.value = "";
this.updateValue();
}
formDisabledCallback(disabled) {
this.input.disabled = disabled;
}
focus() {
this.input.focus();
}
}
customElements.define("custom-text", CustomText);
document
.getElementById("studentForm")
.addEventListener("submit", (event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
alert(
"Student Name: " + formData.get("studentName")
);
});
</script>
</body>
</html>
Explanation of the example
1. The custom element declaration
The element <custom-text> is used in the form instead of a regular <input> element. Its JavaScript class creates and manages an internal text input.
2. Form association
The declaration static formAssociated = true allows the browser to recognize the custom element as a form-associated control.
3. Value management
Whenever the user types into the internal input, the updateValue() method calls setFormValue() to update the value that will be submitted.
4. Required-field validation
If the field is marked as required and remains empty, setValidity() reports a missing value to the browser. This allows normal form validation to prevent submission until the field is corrected.
5. Form reset
The formResetCallback() method clears the field when the form is reset.
6. Submission
When the form is submitted, the example creates a FormData object and retrieves the custom element's value using its name attribute.
The example demonstrates the core concept. A production component would generally need additional styling, accessibility refinements, handling for dynamically changed attributes, and more extensive testing.
5. The Role of the ElementInternals API
The ElementInternals API is central to form-associated custom elements. It gives a custom element access to functionality that ordinary custom elements do not automatically receive.
The most important methods and properties include:
|
Method or property |
Purpose |
|---|---|
|
|
Connects the custom element to its internal browser capabilities. |
|
|
Defines the value or values submitted with the form. |
|
|
Sets the control's validation state and optional validation message. |
|
|
Provides access to the associated form, if one exists. |
|
|
Provides access to labels associated with the custom element. |
|
|
Indicates whether the control is a candidate for constraint validation. |
|
|
Exposes the control's current validity state. |
|
|
Provides the browser's validation message for the control. |
|
|
Checks the element's validity. |
|
|
Checks validity and asks the browser to report a validation failure when appropriate. |
These capabilities help developers build components that work with native form behavior rather than implementing every form feature independently.
For example, a custom date component could use setFormValue() to submit a date and setValidity() to report an invalid date. A custom rating component could submit a numeric rating and report a validation error if the user has not selected a rating.
6. Form Submission and Data Handling
A form-associated custom element can submit its value through the normal HTML form submission mechanism.
For the browser to include the control's value in the submitted data, the element generally needs a name attribute and must set its form value appropriately.
For example:
<form id="feedbackForm">
<custom-rating name="rating"></custom-rating>
<button type="submit">Send Feedback</button>
</form>
If the custom rating component sets its form value to "5", the submitted form data can include:
rating=5
The exact encoding depends on the form's submission method and content type.
A custom element may also submit multiple values. For example, a date-range component could submit separate start and end dates through the FormData interface.
Developers should distinguish between a value displayed by a component and a value actually registered with the browser's form system. Merely showing a value on the screen does not guarantee that it will be included in form submission.
7. Form Validation in Custom Elements
Form validation helps ensure that users provide acceptable information before submitting a form.
Standard HTML controls support validation features such as required, min, max, and pattern. Form-associated custom elements can integrate with the browser's constraint validation system using ElementInternals.
For example, a custom phone-number field may check whether a value meets the application's requirements. If it does not, the component can use setValidity() to mark the field invalid and provide a message explaining the problem.
A simplified example is:
this.internals.setValidity(
{ customError: true },
"Please enter a valid phone number."
);
This marks the element as invalid until its validity state is updated.
When the value becomes valid, the developer can clear the error by calling:
this.internals.setValidity({});
Custom validation should be designed carefully. A browser's built-in validation improves usability, but it does not replace server-side validation. A server must independently validate submitted values because client-side checks can be bypassed.
8. Form Lifecycle Callbacks
Form-associated custom elements support special callbacks that allow developers to respond to changes in the form's state.
formAssociatedCallback(form)
This callback runs when the browser associates the custom element with a form or changes its form association. The argument identifies the associated form, or null when the element is no longer associated with one.
formDisabledCallback(disabled)
This callback runs when the custom element becomes disabled or enabled, including when it is affected by a disabled <fieldset>.
Developers can use it to disable or enable internal controls.
formResetCallback()
This callback runs when the associated form is reset. It allows the component to restore its initial value or another appropriate default state.
formStateRestoreCallback(state, mode)
This callback allows a custom element to restore previously saved state when the browser supports and performs form-state restoration, such as during certain history navigation scenarios.
The state can differ from the submitted value. For example, a custom date picker may submit a normalized date while preserving additional information about the user's unfinished selection.
These callbacks make custom elements better integrated with the browser's normal form lifecycle.
9. Advantages of Form-Associated Custom Elements
Form-associated custom elements offer several important benefits.
Reusable components: Developers can create a custom control once and reuse it across registration forms, checkout pages, feedback forms, and other applications.
Native form integration: Properly implemented components can participate in submission, validation, resetting, and form association without requiring a separate form-handling system.
Greater design flexibility: Developers can create specialized controls, such as star ratings, custom phone inputs, address selectors, and date-range pickers.
Improved maintainability: A component can encapsulate its behavior and internal implementation, making large applications easier to organize.
Accessibility opportunities: Custom controls can provide meaningful labels, validation messages, and focus behavior while integrating with standard browser features. Developers must still implement accessible markup and interaction behavior; form association alone does not guarantee accessibility.
Reduced duplication: Shared custom components can help teams maintain consistent behavior across multiple pages.
10. Limitations and Best Practices
Despite their advantages, form-associated custom elements require careful implementation.
First, browser support should be checked for the target audience. Form-associated custom elements are supported in modern browsers, but compatibility can vary across older versions and environments.
Second, the component must explicitly manage its form value. A visible internal input does not automatically make the outer custom element behave as a successful form control.
Third, validation behavior needs to be implemented correctly. Developers should update validity whenever relevant values or attributes change and provide useful error messages.
Fourth, accessibility must be considered from the beginning. Components should support keyboard navigation, appropriate labeling, clear focus indicators, and understandable error feedback.
Fifth, the component should account for disabled states and form resets. Otherwise, its visible value and submitted value may become inconsistent.
Finally, server-side validation remains essential. Data received from the browser must be checked independently before it is stored or processed.
Conclusion
HTML Form-Associated Custom Elements allow developers to extend standard HTML forms with reusable, specialized controls. By using static formAssociated = true, attachInternals(), setFormValue(), and the browser's validation and lifecycle mechanisms, developers can build custom components that participate in ordinary form submission and validation.
This feature is particularly useful when a project needs controls that go beyond standard HTML inputs but still must work naturally with browser form behavior. It combines the flexibility of Web Components with the convenience of native HTML forms, helping developers create more modular and maintainable web applications.