Web Components in JavaScript
Introduction
Web Components are browser standards for reusable custom elements: Custom Elements, Shadow DOM, and HTML templates. Frameworks like React compile to similar ideas, but native components work without a build step in supporting browsers. This chapter introduces a minimal custom element you can extend later.
Prerequisites
Custom Element Basics
<hello-greeting name="Ada"></hello-greeting>
<script>
class HelloGreeting extends HTMLElement {
connectedCallback() {
const name = this.getAttribute("name") ?? "Guest";
this.textContent = `Hello, ${name}!`;
}
}
customElements.define("hello-greeting", HelloGreeting);
</script>Tag names must contain a hyphen (-) to avoid clashing with built-in tags.
Observed Attributes
class HelloGreeting extends HTMLElement {
static get observedAttributes() {
return ["name"];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "name") {
this.textContent = `Hello, ${newValue ?? "Guest"}!`;
}
}
connectedCallback() {
this.attributeChangedCallback("name", null, this.getAttribute("name"));
}
}
customElements.define("hello-greeting", HelloGreeting);Shadow DOM — Encapsulated Styles
class InfoCard extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>
:host { display: block; padding: 12px; border: 1px solid #ccc; }
h2 { margin: 0; font-size: 1rem; }
</style>
<h2><slot name="title">Title</slot></h2>
<p><slot>Body</slot></p>
`;
}
}
customElements.define("info-card", InfoCard);<info-card>
<span slot="title">Product</span>
Notebook — 80 pages
</info-card>Styles inside shadow root do not leak to the page; page CSS does not pierce shadow by default.
<template> Clone
<template id="row-tpl">
<tr><td class="name"></td><td class="qty"></td></tr>
</template>
<tbody id="rows"></tbody>
<script>
const tpl = document.getElementById("row-tpl");
const tbody = document.getElementById("rows");
const clone = tpl.content.cloneNode(true);
clone.querySelector(".name").textContent = "Pen";
clone.querySelector(".qty").textContent = "2";
tbody.appendChild(clone);
</script>When to Use Web Components
- Design systems shared across multiple frameworks
- Embed widgets on third-party sites
- Progressive enhancement without a bundler
Large apps often still choose React/Vue/Svelte for ecosystem and tooling.
Mini Example: Click Counter Badge
class ClickBadge extends HTMLElement {
#count = 0;
connectedCallback() {
this.#render();
this.addEventListener("click", () => {
this.#count += 1;
this.#render();
});
}
#render() {
this.textContent = `Clicks: ${this.#count}`;
this.style.cursor = "pointer";
}
}
customElements.define("click-badge", ClickBadge);<click-badge></click-badge>FAQ
Browser support?
Custom elements and shadow DOM work in current evergreen browsers—verify targets for enterprise IE policies (legacy).
React and custom elements?
React passes props differently—use wrappers or libraries for seamless integration.
Lifecycle hooks?
connectedCallback, disconnectedCallback, adoptedCallback—clean up listeners in disconnectedCallback.