Web Components Can Now Be Native Form Elements JavaScript in Plain English
Web Components Can Now Be Native Form Elements JavaScript in Plain English
Open in app
This is your last free member-only story this month. Upgrade for unlimited access.
Member-only story
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 1/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
Historically, it has often been hard to style form controls to give them the look and
feel you want.
The styling options are often limited and to this day, form controls like date and
color pickers are still inconsistent across browsers.
Many websites also need more advanced form controls that the native platform
simply doesn’t provide (yet).
Web Components are ideal candidates for customised form controls because they
are first-class HTML tags but unfortunately, they don’t provide the functionality of
built-in form controls out of the box.
For example, when a form is submitted, customised form controls built with
Custom Elements are not included in the form data.
Other functionalities like form validation and autofill are also not available to
Custom Elements and are hard to replicate.
Luckily, there are two solutions available for these issues: the formdata event and the
ElementInternals interface.
FormData event
The formdata event is fired when a form is submitted and enables any JavaScript
code to add arbitrary data to the form data that is submitted.
You can set an event listener for this event that will receive the event object with a
formData property that contains all data being submitted by the form.
Each event listener can then add to or modify the data before it’s submitted:
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 2/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
ElementInternals interface
While the formData event is very handy, it is limited to adding arbitrary data to the
data being submitted by the form.
It enables Custom Elements to be associated with a form so they are added to the
elements property of the form and the value of the element will be automatically
submitted with the form.
The element can indicate whether its value is valid or invalid and prevent the form
from being submitted when it’s invalid.
It can also be labeled with a <label> element which means the label is
programmatically associated with the form element.
When a user clicks or taps the label, the form element will receive focus.
This increases the area for focusing on the element which provides a good user
experience, especially on mobile devices.
It will also cause screen readers to read out the label when the user focuses on the
form element.
In the previous example, you have seen how you can add entries to a FormData
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 3/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
It’s also possible to create a FormData object directly from all elements and their
values in a form by passing the form to the FormData constructor:
formData now contains all data of the form including any Custom Elements that
have been associated with the form.
constructor() {
super();
this.internals = this.attachInternals();
}
get value() {
return this._value;
}
set value(value) {
this._value = value;
this.internals.setFormValue(value);
}
get form() {
return this.internals.form;
}
get name() {
return this.getAttribute(‘name’);
}
get type() {
return this.localName;
}
}
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 4/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
By setting the static formAssociated property to true , the Custom Element will be
associated with the form and it will be included in the form’s elements property.
This won’t however include the value of the Custom Element in the form data yet.
For that, the ElementInternals interface will need to be attached to the element:
this.internals = this.attachInternals();
In order to get the value of the Custom Element that will be submitted with the
form, this value needs to be set with:
this.internals.setFormValue(value);.
In the example, this line has been added to the setter for the value property to
make sure that whenever value is set, its form value will also be set.
The other getters for form , name , and type are standard properties that native form
elements have.
This is the bare minimum your component should have to become a custom form element.
Of course, this component doesn’t render anything yet so it’s not very useful. Let’s
change that by adding a Shadow Root with an <input> element:
constructor() {
super();
this.internals = this.attachInternals();
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 5/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
Now that the component has an <input> , we first need to make sure that the value
of the input is available as the value of the component that is submitted with the
form.
Recall that we created a setter for the value property that calls
this.internals.setFormValue(value) .
We can get the value of the input each time it changes through its change event.
When this event is fired, we simply set the value property of our component which
will call the setter which calls this.internals.setFormValue(value) .
this.input = this.shadowRoot.querySelector(‘input’);
Now every time the value of the input changes, the value of the component is
updated and the correct value will be submitted with the form.
Of course, the other way around should also work: when the value property of the
component is set, the value property of the <input> should also be set.
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 6/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
set value(value) {
this._value = value;
this.input.value = value; // set the value of the input
this.internals.setFormValue(value);
}
And the code that uses our component will also expect a change event to be fired
since it now contains an <input> element.
We can’t simply dispatch it again, since that will throw an error saying the event was
already dispatched but we can clone it and then forward it:
Now the value of the component and the input are kept in sync and the component
fires a change event.
The benefit of this is that the Custom Element will also be programmatically
associated with the label.
This means, for example, that a screen reader will read out the label text when a
user focuses the input and that when the label is clicked or tapped, the associated
input will be focused.
This increases the touch area of the input, making it easier to focus, especially on a
mobile device.
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 7/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
<label>
City
<input type=”text” name=”city”>
</label>
The value of this attribute should be the id of the form control you want to
associate it with:
<label for=”city”>City</label>
<input type=”text” id=”city”>
This may come in handy when the label and the form control are in different places
in the DOM or when you can’t place the control inside the label for some reason.
There are two additional things that need to be done to make clicking the label
focus the <input> inside your custom form control.
First, the component should be made focusable. This is done by adding a tabindex
attribute.
This attribute indicates that an element is focusable and the order in which
elements will be focused when a user uses the TAB key to navigate through form
controls.
Normally, the focus order relies on the order in which the elements occur in the
DOM but with tabindex this can be altered.
It means that for example an element with tabindex 4 will be focused before an
element with tabindex 5 but after an element with tabindex 3.
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 8/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
When two elements have the same tabindex the order in the DOM takes precedence
so if you want to keep this order but need to add tabindex to make elements
focusable, you can use tabindex="0" for all of them.
To make sure the element always has a tabindex attribute, you can check if it’s
present and add it if it’s not:
if (!this.hasAttribute(‘tabindex’)) {
this.setAttribute(‘tabindex’, ‘0’);
}
Now that your component is focusable, it will be focused when the <label> it’s
associated with is clicked or tapped.
But since the <input> inside it needs to be focused, the focus should be delegated
with the following code:
Now when the <label> is clicked, the <input> inside the component will be
focused.
constructor() {
super();
this.internals = this.attachInternals();
const shadowRoot = this.attachShadow({mode: ‘open’});
shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
}
input {
display: block;
padding: 5px;
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 9/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
}
</style>
<input type=”text”>
`;
}
connectedCallback() {
this.input = this.shadowRoot.querySelector(‘input’);
this.input.addEventListener(‘change’, (e) => {
const clone = new e.constructor(e.type, e);
this.dispatchEvent(clone);
this.value = this.input.value;
});
this.addEventListener(‘focus’, () => this.input.focus());
if (!this.hasAttribute(‘tabindex’)) {
this.setAttribute(‘tabindex’, ‘0’);
}
}
get value() {
return this._value;
}
set value(value) {
this._value = value;
this.internals.setFormValue(value);
}
get form() {
return this.internals.form;
}
get name() {
return this.getAttribute(‘name’);
}
get type() {
return this.localName;
}
}
HTML JS Result
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 10/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
Be sure to check my follow-up article that explains how Web Components can
participate in native form validation.
Join Modern Web Weekly, my weekly newsletter on the modern web platform, web
components and Progressive Web Apps.
Follow
I write about what the modern web is capable of, Web Components and PWA, creator of
https://whatpwacando.today
39
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 12/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
1.5K 26
14.1K 347
78 1
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 14/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
299 2
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 15/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
225 1
Lists
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 16/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
81
JP Brown
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 17/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
23K 276
108
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 18/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English
52
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 19/19