The most popular way to make elements is to use the Autonomous Custom Element style, by making up your own tag and
extending HTMLElement
. Each style comes with different trade-offs. Choosing a type of component to build will depend
on a lot of factors. Autonomous Custom Elements give you a blank canvas to work with. Customized Built-ins
extend the element you're customizing. Here are some considerations to think about:
Tag Name
Perhaps the most obvious difference between the two is that Autonomous Custom Elements get to create an entirely new
tag name, and this means if you're querying for the element in the DOM, you'll need to reference that tag name, e.g.
.querySelector('fancy-button')
will return the first <fancy-button>
.
With Customized Built-in elements, the tag name must match the element you're customizing. For example if you wanted
to customize a <button>
element then your HTML will take the shape of <button is="fancy-button>
. In order to query
this element you'd need to use an attribute selector, for example .querySelector('button[is="fancy-button"]')
. This
also means existing code that queries for button
elements will also find your Customized Built-ins. Calling
.querySelectorAll('button')
will find all button elements, including ones which are Customized Built-in elements.
The way to find non-Customized Built-ins is to use a selector like: .querySelectorAll('button:not([is])')
.
This difference in tag name also effects how you'll select for these elements in CSS. It also has some additional CSS considerations...
CSS & Styling
Given Autonomous Custom Elements have their own tag, they are unlikely to conflict with existing CSS. They can have classes added to them, and so can be styled by existing CSS but it's opt-in.
As Customized Built-ins keep their tags (e.g. <button is="fancy-button">
) any CSS that has rules like button {}
will apply. This means if you have some existing CSS that applies to built-ins, it'll also apply to the Customized
built-ins. This also includes the default user agent CSS.
All built-ins have some user-agent CSS supplied already, for example div
elements have display: block;
,
<button>
elements are styled to look like your operating system's buttons. Customized Built-ins will also get these
styles, so <button is="fancy-button">
will look the same as <button>
until you customize it further.
If you want to customize a built-in by applying only new styles and not adding any new logic, it might be best to use a CSS class instead. Customized Built-ins are best used when extending or adding new logic.
If you create an Autonomous Custom Element (e.g. <fancy-button>
) you'll need to style it from scratch as Autonomous
Custom Elements have no default CSS. You will probably want to add some CSS to these elements - even if it's just
display: block
.
One other thing to think about with regard to styling is encapsulated styles within the ShadowDOM...
ShadowDOM
Autonomous Custom Elements can make use of the ShadowDOM. Only a limited set of built-ins can use the ShadowDOM. If you want to alter any nested elements, it's a great idea to use ShadowDOM, and so you probably won't want to customize a built-in. Here's a list of built-ins that you can customize with ShadowDOM:
<article>
<aside>
<blockquote>
<body>
<div>
- The
<header>
and<footer>
elements - Heading elements
<h1>
,<h2>
,<h3>
,<h4>
,<h5>
, and<h6>
<main>
<nav>
<p>
<section>
<span>
ShadowDOM can be really useful for providing encapsulated markup and styles. Styles within ShadowDOM don't affect the rest of the page, and so it can be a really useful place to add styles to your elements. If this is a high priority, you might find using an Autonomous Custom Element to be a better choice than the limited set of built-ins which can use ShadowDOM.
ShadowDOM also provides elements with the ability to choose how nested elements render. An ability that many built-ins already have...
Nesting & Semantics
Many built-in elements will only allow certain tags to nest inside (you can read more about Content Categories on
MDN). For example a <button>
tag only allows [phrasing content] tags like <b>
, <strong>
,
<span>
and so on. Some elements, for example the <details>
element will have specific associations with other
elements. A <summary>
tag can only exist as the first child to a <details>
element, and if it doesn't exist, it
will be created by the <details>
tag.
Customized Built-ins match the semantics of the built-in they're customizing, and that cannot be changed. So for
example a <button is="fancy-button">
will only allow nested phrasing content tags just like a
<button>
.
Autonomous Custom Elements allow any nested tag by default. This can be customized with ShadowDOM, but the default
behavior is to allow any nested element. An element like <fancy-button>
could include any flow
content tags. It might be weird to see a <fancy-button>
with an <iframe>
nested inside.
Behavior & API
Autonomous Custom Elements must extend from HTMLElement
. They'll get all the methods and properties inherited from
that, for example it will include functions like .querySelector()
, .addEventListener()
, or .focus()
. They will
include properties like .hidden
, .inert
, .lang
, or .dir
. They will also trigger the regular events that all
elements do, for example click
, mousemove
, animationend
.
Customized Built-ins will have even more on top. They inherit the class related to their tag, and so elements
customizing <button>
need to extend HTMLButtonElement
, customizing <video>
means extending HTMLVideoElement
.
These all extend from HTMLElement
themselves so you'll still get everything HTMLElement
does, but with even more.
Extending from HTMLButtonElement
, for example, means your class will inherit the button's properties, like .type
,
.disabled
, .forms
, .name
, .value
, and so on. On one hand this would be a lot of code to replicate yourself with
an Autonomous Custom Element, but your element adopting all these might not be desirable.
When extending from a Customized Built-in overriding the existing methods and properties can have undesirable
consequences. It might be tempting to add new .type
values to <button>
for example, but in doing so you might run
into issues with code that isn't expecting to see the newly added logic.
Accessibility
Customized Built-ins have very good accessibility information built right into them. Most have an implicit role which means that assistive technologies know how to interpret them. For example using a screen reader, it's possible to navigate through all the headings in a web page, and the purpose of form controls is explained as each one is focused (e.g. buttons are announced not only by their label but also referred to as "buttons").
Autonomous Custom Elements, do not have any accessibility information built into them. Assistive technologies such as
screen readers will read the contents of the element as if it were plain text; treating it the same as a <div>
or
<span>
. It's possible to customize how assistive technology like screen readers handle your element by using the
Accessible Rich Internet Applications (or ARIA) APIs, such as the role=
attribute.
Accessibility can be hard to get right. Many assistive tools behave differently, and much like browsers, support is not universal and consistent. It's always worth getting comfortable with these tools, and testing your web applications using a variety of them. A lot of work has gone into making the built-ins as accessible as possible by default, and so it can be a good idea to rely on those defaults.
Summary
This has all been a lot to go over. The truth is there's good reasons to pick customizing a built-in, but it should be carefully considered. If your element is substantially different from any existing element, then using an Autonomous Custom Element is a good choice. To help drive your decision, here's a table summarizing the above information:
This section is incomplete! You can help out by contributing documentation here !