Customized Built-ins
Two kinds of custom elements
Section titled “Two kinds of custom elements”The Web Components spec defines two flavors of custom element:
| Flavor | Extends | Registered as | Used in HTML |
|---|---|---|---|
| Autonomous | HTMLElement | customElements.define('x-btn', XBtn) | <x-btn> |
| Customized built-in | e.g. HTMLButtonElement | customElements.define('x-btn', XBtn, { extends: 'button' }) | <button is="x-btn"> |
Customized built-ins: syntax
Section titled “Customized built-ins: syntax”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.
The support caveat
Section titled “The support caveat”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>The portable choice: autonomous elements
Section titled “The portable choice: autonomous elements”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.
Runnable demo (autonomous element)
Section titled “Runnable demo (autonomous element)”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.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
Customized built-in (is="...") | Inherits the original element’s native semantics in full (focus, form submission, accessibility) with no code of your own | Safari permanently declines to support it, so it isn’t portable cross-browser |
Autonomous element (extends HTMLElement) | Fully supported in every major browser | You have to write the native semantics yourself (e.g. role, keyboard handling) |
Common mistakes
Section titled “Common mistakes”- Forgetting
{ extends: 'button' }— without the third argument tocustomElements.define, the browser doesn’t know this is a customized built-in andis="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-elementsfixes 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)