0% found this document useful (0 votes)
12 views

Web Components Can Now Be Native Form Elements JavaScript in Plain English

The document discusses how Web Components can now function as native form elements, allowing developers to create customized form controls that integrate with standard form functionalities like validation and data submission. It introduces the formdata event and ElementInternals interface as solutions for ensuring custom elements can participate in form submissions and validation. The article provides a detailed guide on creating a form-associated Custom Element, including code examples and best practices for labeling and focusing elements.

Uploaded by

anoop.jain
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views

Web Components Can Now Be Native Form Elements JavaScript in Plain English

The document discusses how Web Components can now function as native form elements, allowing developers to create customized form controls that integrate with standard form functionalities like validation and data submission. It introduces the formdata event and ElementInternals interface as solutions for ensuring custom elements can participate in form submissions and validation. The article provides a detailed guide on creating a form-associated Custom Element, including code examples and best practices for labeling and focusing elements.

Uploaded by

anoop.jain
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 19

6/28/23, 4:48 PM 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

Web Components Can Now Be Native Form


Elements
Here’s the complete guide to customised form controls

Danny Moerkerke · Follow


Published in JavaScript in Plain English
7 min read · Aug 10, 2022

Listen Share More

Photo by Chris Hainto on Unsplash

Custom Form Controls


One area in which developers have always wanted to customize elements is forms.

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:

const form = document.querySelector(‘form’);


form.addEventListener(‘formdata’, ({formData}) => {
// add data
formData.append(‘custom-control’, customValue);
// modify data
formData.set(‘email’, formData.get(‘email’).toLowerCase());
});

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

The formData property of the event is a FormData object.

ElementInternals interface
While the formData event is very handy, it is limited to adding arbitrary data to the
data being submitted by the form.

For a Custom Element to be a true form control it should also be automatically


associated with the form and participate in form validation.

That is what the ElementInternals interface is for.

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.

It also enables Custom Elements to participate in form validation.

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.

Associating a Custom Element with a form


The first step to creating a custom form control with a Custom Element is to
associate it with the form.

This means it will be part of the form’s elements property, which is an


HTMLFormControlsCollection containing all form controls contained by the form.

In the previous example, you have seen how you can add entries to a FormData

object using its append method.

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:

const form = document.querySelector(‘form’);


// formData now contains all data of form
const formData = new FormData(form);

formData now contains all data of the form including any Custom Elements that
have been associated with the form.

Here’s the class of a form-associated Custom Element:

class FormInput extends HTMLElement {


static formAssociated = true;

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;
}
}

This example shows a minimal implementation of a form-associated Custom


Element. Let’s look at the individual parts.

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

static formAssociated = true;

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();

attachInternals returns the ElementInternals object which is then stored in the


internals property of the Custom Element.

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.

This is the value that will be submitted with the form.

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

const shadowRoot = this.attachShadow({mode: ‘open’});


shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
}
input {
display: block;
padding: 5px;
}
</style>
<input type=”text”>
`;
}

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) .

Let’s add the needed event listener:

this.input = this.shadowRoot.querySelector(‘input’);

this.input.addEventListener(‘change’, (e) => {


this.value = this.input.value;
});

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

The setter should be changed to this:

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.

Therefore, the event from the <input> should be forwarded.

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:

this.input.addEventListener(‘change’, (e) => {


const clone = new e.constructor(e.type, e); // clone the event

this.dispatchEvent(clone); // and then forward it


this.value = this.input.value;
});

Now the value of the component and the input are kept in sync and the component
fires a change event.

Labeling the custom form control


Now that you have associated your Custom Element with the form, you can also
associate it with a <label> .

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

There are two ways of associating a form control with a <label> .

The simplest is to place the form control inside the <label> .

This makes the association very clear:

<label>
City
<input type=”text” name=”city”>
</label>

The other way is to use the for attribute of the <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:

this.addEventListener(‘focus’, () => this.input.focus());

Now when the <label> is clicked, the <input> inside the component will be
focused.

Here’s the complete component:

class FormInput extends HTMLElement {


static formAssociated = true;

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;
}
}

And here’s a working codepen:

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

Custom Element form control

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.

JavaScript Web Development Web Components Programming Coding

Follow

Written by Danny Moerkerke


1.6K Followers · Writer for JavaScript in Plain English
https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 11/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English

I write about what the modern web is capable of, Web Components and PWA, creator of
https://whatpwacando.today

More from Danny Moerkerke and JavaScript in Plain English

Danny Moerkerke in ITNEXT

How To Hydrate A Server-Side Rendered Web Component


An in-depth guide to lazy loading Web Components

· 12 min read · May 11

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

The woman in JavaScript in Plain English

A 20-year-experienced CTO’s Advice “Don’t Be a Humble Developer”


I think 90% of the developers are victims of their modesty!!

· 4 min read · Jun 12

1.5K 26

Somnath Singh in JavaScript in Plain English

Bill Gates: People Don’t Realize What’s Coming


https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 13/19
6/28/23, 4:48 PM Web Components Can Now Be Native Form Elements | JavaScript in Plain English

Tech Jobs Won’t Exist in 5 Years

· 13 min read · Apr 13

14.1K 347

Danny Moerkerke in ITNEXT

How To Server-Side Render A Web Component


Blazing fast lazy-loaded Web Components

· 6 min read · Apr 29

78 1

See all from Danny Moerkerke

See all from JavaScript in Plain English

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

Recommended from Medium

Danny Moerkerke in ITNEXT

The Hidden Power of Custom States For Web Components


A crucial step in the evolution of Custom Elements

· 6 min read · Nov 16, 2022

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

Jennifer Fu in Better Programming

Ant Design System Dark Mode and Other Theme Customization


It takes one line of code to set up dark mode for an Ant Design System app

· 12 min read · Feb 21

225 1

Lists

General Coding Knowledge


20 stories · 27 saves

Stories to Help You Grow as a Software Developer


19 stories · 149 saves

It's never too late or early to start something


10 stories · 9 saves

Coding & Development


11 stories · 15 saves

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

pandaquests in Level Up Coding

Difference between service workers and web workers


Web workers have been quite around for a while. Service worker is a new-ish concept. Both
enable you to run JavaScript in the background…

· 4 min read · Jan 3

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

What Really Happens to a Human Body at Titanic Depths


A Millisecond-by-Millisecond Explanation

· 4 min read · 5 days ago

23K 276

Rakia Ben Sassi in Level Up Coding

Unleash the Cheetah: A Real-World Case Study of Optimizing Angular


Rendering Performance
Boost your application’s speed and improve user experience with these proven techniques

· 11 min read · Feb 6

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

Israel Miles in Level Up Coding

Build Reusable and Customized Web Components Using Stencil.js


Increase your web development flexibility with your own unique HTML tags.

· 3 min read · Jan 8

52

See more recommendations

https://javascript.plainenglish.io/web-components-can-now-be-native-form-elements-107c7a93386 19/19

You might also like