Extending Native Elements
Tags: javascript, webcomponents, a11y, webdev
Published: 2022-01-09 09:38:01

If you are familiar with [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components) or [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) then you might have heard that you can not only extend from [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) but also from other native elements such as [HTMLInputElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement).

```js
class myInput extends HTMLInputElement {
   ...
}
customElements.define("my-input", myInput, { extends: 'input' });
```
and used like this:

```js
<input type="text" is="my-input">
```

The benefits of extending an existing native element are many:
1. Your customers will be familiar with the properties, attributes and events of that new component
1. You will have a very clear set of rules to follow
1. Frameworks such as Angular or Vue.js will know how to deal with this component

Not on the list of benefits, but is definitely a pro… if is an old browser such as Internet Explorer 11, then the element like the input will still render in its usual way. If is a text input it will look like a text input. So it can only look better as long as you load the module after checking the type of browser.

The implementation is a bit different than a normal Custom Element and the reason for that is that we can’t draw the current element (this) as it is already rendered. We can’t also remove it from the DOM as this is the communication channel between the browser and our component. We can’t also add child elements to it because elements like input don’t accept children. So what do we do?
You can add a sibling element and if is necessary you can hide the current element.

```js
render(){
  this.style.display = "none";
  this.wrapperEl = document.createElement("div");
  this.wrapperEl.innerHTML = `<div>My Input</div>`;
  this.after(this.wrapperEl);
}
```

You need to remember to remove the sibling element when the component is removed from the DOM:
```js
disconnectedCallback() {
  this.wrapperEl.remove();
}
```

Regarding **accessibility**, if you are making an input, I suggest making sure the main element you are drawing is focusable (add tabIndex = 0) to make sure the element will be in focus when navigating between inputs but make sure the internal navigation if you have, is not focusable. The idea in a form is that by pressing TAB you can jump from one field to another. Internally (when the user is focused on the actual component) you can enable the keys to navigate internally like up/down. Like the Select.

One of the components I’ve implemented, I made an image browser, so the value would be the image ID and it extends input.
It looks pretty similar to a text input except for the fact that it has a browse button and that opens a modal window that allows browsing. The component depends on Bootstrap to open the modal. In this case, also the modal needs to be removed on disconnected if needed.

You need to be aware of the **events** you should be throwing when the data is updated like change, input, etc. When you update the value property you also need to dispatch the relevant event as it won’t be dispatched automatically:

```js
this.dispatchEvent(new Event("input"));
```

I personally don’t like using frameworks for components. I think if the code is too complicated it means that something is wrong and it should be split into smaller components or classes. But that’s me, the important thing is that no matter your implementation, the outcome will be a simple way for your customers to use a component. What’s behind the black box is up to you and not up to the customer.

I hope it helped you and I would love to read your comments and suggestions.

Best of luck ;-)

_This article is based on the one I wrote in https://smartcodehub.com/extending-native-elements-f151584699a2_