Mastering Custom Elements in HTML5
Custom Elements are part of the Web Components standard and allow you to create reusable, encapsulated HTML tags with your own behavior and styling. They help you build modular UI components without relying on heavy frameworks.
What Are Custom Elements?
Custom elements let you define new HTML tags backed by JavaScript classes. They support:
- Encapsulated styles via Shadow DOM
- Custom attributes
- Lifecycle callbacks
- Reusable component logic
Custom elements belong to two types:
- Autonomous elements (e.g.,
<user-card>) - Customized built‑in elements extending built‑in tags (e.g.,
<button is="fancy-button">)
1. Creating a Basic Custom Element
Step 1: Define a Class
js
class MyCustomElement extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>
p { color: blue; font-weight: bold; }
</style>
<p>Hello, I am a custom element!</p>
`;
}
}Step 2: Register the Element
js
customElements.define("my-custom-element", MyCustomElement);Step 3: Use It in HTML
html
<my-custom-element></my-custom-element>2. Handling Attributes with attributeChangedCallback
js
class AlertBox extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `<p>Default alert</p>`;
}
static get observedAttributes() {
return ["message"];
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === "message") {
this.shadowRoot.querySelector("p").textContent = newVal;
}
}
}
customElements.define("alert-box", AlertBox);Usage:
html
<alert-box message="This is a custom alert!"></alert-box>3. Shadow DOM Encapsulation Example
js
class FancyBox extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
div { padding: 16px; border: 2px solid green; }
</style>
<div><slot></slot></div>
`;
}
}
customElements.define("fancy-box", FancyBox);html
<fancy-box>Inside Fancy Box</fancy-box>4. Customized Built‑In Elements
js
class HighlightedButton extends HTMLButtonElement {
constructor() {
super();
this.style.backgroundColor = "yellow";
this.style.fontWeight = "bold";
}
}
customElements.define("highlighted-button", HighlightedButton, { extends: "button" });Usage:
html
<button is="highlighted-button">Click Me</button>5. Advanced Example 1 — Toggle Switch
js
class ToggleSwitch extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
.switch {
width: 50px;
height: 25px;
border-radius: 20px;
background: #ccc;
position: relative;
cursor: pointer;
}
.knob {
width: 22px;
height: 22px;
background: white;
border-radius: 50%;
position: absolute;
top: 1.5px;
left: 1.5px;
transition: transform .2s;
}
.on { background: #4caf50; }
.on .knob { transform: translateX(25px); }
</style>
<div class="switch"><div class="knob"></div></div>
`;
this.switch = this.shadowRoot.querySelector(".switch");
this.switch.addEventListener("click", () => this.toggle());
}
toggle() {
this.switch.classList.toggle("on");
this.dispatchEvent(new CustomEvent("change", { detail: this.isOn() }));
}
isOn() {
return this.switch.classList.contains("on");
}
}
customElements.define("toggle-switch", ToggleSwitch);Usage:
html
<toggle-switch></toggle-switch>6. Advanced Example 2 — User Card
js
class UserCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
const name = this.getAttribute("name") || "Unknown";
const avatar = this.getAttribute("avatar") || "https://placehold.co/80";
this.shadowRoot.innerHTML = `
<style>
.card {
border: 1px solid #ddd;
padding: 12px;
border-radius: 8px;
display: flex;
gap: 12px;
align-items: center;
}
img { border-radius: 50%; width: 60px; height: 60px; }
</style>
<div class="card">
<img src="${avatar}" />
<div>
<h3>${name}</h3>
<slot></slot>
</div>
</div>
`;
}
}
customElements.define("user-card", UserCard);Usage:
html
<user-card name="Anton" avatar="/me.png">
<p>Frontend Developer</p>
</user-card>7. Advanced Example 3 — Modal Window
js
class CustomModal extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
.overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,.5);
display: none;
justify-content: center;
align-items: center;
}
.modal {
background: white;
padding: 20px;
border-radius: 8px;
}
.open { display: flex; }
</style>
<div class="overlay">
<div class="modal">
<slot></slot>
<button id="close">Close</button>
</div>
</div>
`;
this.overlay = this.shadowRoot.querySelector(".overlay");
this.shadowRoot.querySelector("#close").onclick = () => this.hide();
}
show() { this.overlay.classList.add("open"); }
hide() { this.overlay.classList.remove("open"); }
}
customElements.define("custom-modal", CustomModal);Usage:
html
<custom-modal id="modal">Hello from modal!</custom-modal>
<script>
document.getElementById("modal").show();
</script>Conclusion
Custom elements let you extend HTML with your own reusable components. With Shadow DOM, lifecycle callbacks, and custom attributes, Web Components allow you to build modern, framework‑agnostic UI components that work anywhere.