Skip to content

Customized Built-ins

The Web Components spec defines two flavors of custom element:

FlavorExtendsRegistered asUsed in HTML
AutonomousHTMLElementcustomElements.define('x-btn', XBtn)<x-btn>
Customized built-ine.g. HTMLButtonElementcustomElements.define('x-btn', XBtn, { extends: 'button' })<button is="x-btn">

A customized built-in extends a specific HTML element class and declares which built-in it extends in the third argument to define:

class FancyButton extends HTMLButtonElement {
connectedCallback() {
this.style.background = '#4f46e5';
this.style.color = '#fff';
this.style.borderRadius = '6px';
this.style.padding = '.4rem 1rem';
this.style.border = 'none';
this.style.cursor = 'pointer';
}
}
customElements.define('fancy-button', FancyButton, { extends: 'button' });

In HTML, the is attribute activates the customized built-in on a standard element:

<button is="fancy-button">Click me</button>

The element is still a <button> — it inherits all the built-in semantics (focusable, submits forms, responds to disabled) plus your custom behavior.

Safari does not support customized built-ins and has explicitly declined to implement them. This is a significant portability problem: a <button is="fancy-button"> will render as a plain unstyled button in Safari.

There are polyfills (such as @ungap/custom-elements), but they add weight and complexity.

<!-- This renders correctly in Chrome/Firefox but NOT in Safari: -->
<button is="fancy-button">Safari renders this as a plain button</button>

Autonomous custom elements (extends HTMLElement) are fully supported everywhere. For the button example, an equivalent autonomous element:

class XButton extends HTMLElement {
connectedCallback() {
this.innerHTML =
'<button style="background:#4f46e5;color:#fff;border-radius:6px;' +
'padding:.4rem 1rem;border:none;cursor:pointer">' +
(this.textContent || 'Click me') + '</button>';
}
}
customElements.define('x-button', XButton);

You lose the automatic inheritance of native <button> semantics (you must add role="button" and keyboard handling manually), but you gain full cross-browser support.

The demo below uses an autonomous element — it works in every browser. The customized built-in syntax is shown for reference only.

Browser note: The customized built-in pattern (class extends HTMLButtonElement + <button is="...">) is not supported in Safari. Use autonomous elements (class extends HTMLElement) for portable, cross-browser Web Components.

OptionBenefitCost
Customized built-in (is="...")Inherits the original element’s native semantics in full (focus, form submission, accessibility) with no code of your ownSafari permanently declines to support it, so it isn’t portable cross-browser
Autonomous element (extends HTMLElement)Fully supported in every major browserYou have to write the native semantics yourself (e.g. role, keyboard handling)
  • Forgetting { extends: 'button' } — without the third argument to customElements.define, the browser doesn’t know this is a customized built-in and is="fancy-button" does nothing.
  • Not testing in Safari before shipping — the failure is silent, no console error, just a plain unstyled <button> — easy to miss if you only test Chrome/Firefox.
  • Adding a polyfill without weighing the cost — @ungap/custom-elements fixes the gap, but it adds bundle size and complexity to every request, even in browsers that already support it natively.

💡 Real-world examples

GitHub — chose to build <relative-time> and <clipboard-copy> as autonomous elements instead of customized built-ins, so they work in every browser including Safari. (github/relative-time-element)

Adobe Spectrum — the Spectrum Web Components design system uses autonomous elements as its default, for the same cross-browser portability reason. (opensource.adobe.com)

How do you activate a customized built-in element in HTML?
Which third argument to customElements.define signals a customized built-in?
Which browser does NOT support customized built-in elements?