Skip to content

The is Attribute

Global Attribute

The is attribute allows you to create customized built-in elements by extending existing HTML elements with custom behavior through Web Components. Unlike autonomous custom elements, customized built-in elements inherit all the functionality and accessibility features of their base element.

<button is="custom-button">Enhanced Button</button>
<input is="custom-input" type="text">
ValueDescription
Custom element nameName of the customized built-in element (must contain a hyphen)

Customized Built-in vs Autonomous Custom Elements

Section titled “Customized Built-in vs Autonomous Custom Elements”

Customized Built-in Element:

<!-- Extends native <button> -->
<button is="fancy-button">Click me</button>

Autonomous Custom Element:

<!-- Completely new element -->
<fancy-button>Click me</fancy-button>

Inherit native behavior: Get all browser defaults for free ✓ Accessibility: Native ARIA roles and keyboard support ✓ Progressive enhancement: Falls back to standard element if JS fails ✓ Semantic HTML: Keep semantic meaning of base element

Result
Result
Result
class MyButton extends HTMLButtonElement {
constructor() {
super(); // Always call super() first
// Your custom initialization
}
connectedCallback() {
// Called when element is added to DOM
}
disconnectedCallback() {
// Called when element is removed from DOM
}
attributeChangedCallback(name, oldValue, newValue) {
// Called when observed attributes change
}
}
// Register the element
customElements.define('my-button', MyButton, {
extends: 'button' // IMPORTANT: Specify base element
});
<!-- Use with "is" attribute -->
<button is="my-button">Click Me</button>
<!-- Or create programmatically -->
<script>
const btn = document.createElement('button', { is: 'my-button' });
btn.textContent = 'Click Me';
document.body.appendChild(btn);
</script>

You can extend most HTML elements:

// Extend button
class FancyButton extends HTMLButtonElement { }
customElements.define('fancy-button', FancyButton, { extends: 'button' });
// Extend input
class MaskedInput extends HTMLInputElement { }
customElements.define('masked-input', MaskedInput, { extends: 'input' });
// Extend select
class SuperSelect extends HTMLSelectElement { }
customElements.define('super-select', SuperSelect, { extends: 'select' });
// Extend div
class SmartDiv extends HTMLDivElement { }
customElements.define('smart-div', SmartDiv, { extends: 'div' });
// Extend ul
class TodoList extends HTMLUListElement { }
customElements.define('todo-list', TodoList, { extends: 'ul' });
// ✓ Good: Contains hyphen
customElements.define('my-button', MyButton, { extends: 'button' });
// ✗ Bad: No hyphen
customElements.define('mybutton', MyButton, { extends: 'button' });
class MyElement extends HTMLButtonElement {
constructor() {
super(); // REQUIRED
// Your code...
}
}
<!-- Falls back to regular button if JS fails -->
<button is="fancy-button">Still works without JS!</button>
class BetterButton extends HTMLButtonElement {
constructor() {
super();
// Add behavior, don't break existing functionality
this.addEventListener('click', this.customBehavior.bind(this));
}
customBehavior() {
// Additional functionality
console.log('Custom behavior');
// Native click behavior still works
}
}
class PhoneInput extends HTMLInputElement {
constructor() {
super();
this.addEventListener('input', this.formatPhone.bind(this));
}
formatPhone() {
let value = this.value.replace(/\D/g, '');
if (value.length > 10) value = value.slice(0, 10);
if (value.length >= 6) {
this.value = `(${value.slice(0, 3)}) ${value.slice(3, 6)}-${value.slice(6)}`;
} else if (value.length >= 3) {
this.value = `(${value.slice(0, 3)}) ${value.slice(3)}`;
} else {
this.value = value;
}
}
}
customElements.define('phone-input', PhoneInput, { extends: 'input' });
<input is="phone-input" type="tel" placeholder="Phone number">
class CopyButton extends HTMLButtonElement {
constructor() {
super();
this.addEventListener('click', this.copy.bind(this));
}
async copy() {
const target = this.dataset.copyTarget;
const element = document.getElementById(target);
if (element) {
await navigator.clipboard.writeText(element.textContent);
this.textContent = 'Copied!';
setTimeout(() => this.textContent = 'Copy', 2000);
}
}
}
customElements.define('copy-button', CopyButton, { extends: 'button' });
<pre id="code">console.log('Hello');</pre>
<button is="copy-button" data-copy-target="code">Copy</button>
  1. Use autonomous custom elements instead
  2. Use a polyfill: @webcomponents/custom-elements
  3. Feature detection:
function supportsCustomizedBuiltInElements() {
try {
customElements.define('test-button', class extends HTMLButtonElement {}, { extends: 'button' });
return true;
} catch {
return false;
}
}
if (supportsCustomizedBuiltInElements()) {
// Use customized built-in elements
} else {
// Use autonomous custom elements or fallback
}
BrowserSupport
Chrome67+
Firefox63+
Edge79+
Safari❌ Not supported