Custom Elements Overview
The Custom Elements API lets you define your own HTML tags — like
<user-card> or <fancy-button> — backed
by a JavaScript class. Once registered, a custom element behaves like any
built-in element: the browser parses it, you can style it with CSS, and it
participates in the normal DOM lifecycle. This is the foundation of the
Web Components family, alongside Shadow DOM, <template>,
and <slot>.
Registering an Element with customElements.define
You register a custom element by calling
customElements.define(name, class), passing the tag name and a
class that extends HTMLElement (or another built-in element
class).
basic-custom-element.html
<script>
class HelloBanner extends HTMLElement {
connectedCallback() {
this.textContent = 'Hello from a custom element!'
this.style.display = 'block'
this.style.padding = '12px'
this.style.background = '#eef6ff'
}
}
customElements.define('hello-banner', HelloBanner)
</script>
<hello-banner></hello-banner>Rendered as a styled block: "Hello from a custom element!"
The Hyphen Naming Rule
Every custom element's tag name must contain a hyphen (e.g.
my-widget, user-card, x-tabs). This
is a permanent rule in the HTML spec, not a convention — it guarantees that
no future built-in HTML element will ever collide with a name you choose,
since native tags never contain a hyphen.
Name | Valid? | Why |
|---|---|---|
| Yes | Contains a hyphen |
| Yes | Contains a hyphen |
| No | No hyphen — invalid custom element name |
| No | Reserved built-in element name |
customElements.define('widget', ...) (no hyphen) throws a DOMException at runtime. The hyphen requirement is enforced by the browser, not just a style guide.Autonomous vs Customized Built-in Elements
There are two flavors of custom element.
Kind | Extends | Used as |
|---|---|---|
Autonomous custom element |
| Its own brand-new tag, e.g. |
Customized built-in element | A specific built-in class, e.g. | An existing tag plus an |
customized-builtin.html
<script>
class FancyButton extends HTMLButtonElement {
connectedCallback() {
this.style.borderRadius = '999px'
this.style.padding = '8px 20px'
}
}
customElements.define('fancy-button', FancyButton, { extends: 'button' })
</script>
<button is="fancy-button">Click me</button>is="..." form) are not supported in every browser (notably Safari has historically lagged here). Autonomous custom elements have broad, solid support across modern browsers.Lifecycle Callbacks (Conceptual Overview)
A custom element class can implement several special methods that the browser calls automatically at key moments. You never call these yourself — the browser invokes them for you.
Callback | Called when |
|---|---|
| The element is inserted into the document (can fire more than once if moved) |
| The element is removed from the document |
| One of the attributes listed in |
| The element is moved into a new document (e.g. via |
lifecycle-callbacks.js
class StatusPill extends HTMLElement {
static get observedAttributes() {
return ['status']
}
connectedCallback() {
console.log('Added to the page')
this.render()
}
disconnectedCallback() {
console.log('Removed from the page')
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(`${name} changed from ${oldValue} to ${newValue}`)
this.render()
}
render() {
this.textContent = this.getAttribute('status') || 'unknown'
}
}
customElements.define('status-pill', StatusPill)connectedCallback is the right place to read attributes, render content, and set up event listeners.Putting the Pieces Together
Custom Elements rarely stand alone. In real Web Components they are
typically combined with a <template> for markup, an
attached Shadow DOM for encapsulation, and <slot> for
content projection.
Custom Elements — define new tag names backed by a class.
<template>— hold the reusable, inert markup for the element.Shadow DOM — encapsulate that markup and its styles from the page.
<slot>— let consumers project their own content into the encapsulated tree.
Quick Reference
Concept | Detail |
|---|---|
Register |
|
Naming rule | Tag name must contain a hyphen |
Base class |
|
Key lifecycle hook |
|
Watch attribute changes | Declare |