Skip to main content

What are Web Components?

Answer

Web Components are a set of web platform APIs that allow you to create reusable, encapsulated custom HTML elements with their own styling and behavior.

The Three Technologies

Creating a Custom Element

// Define the custom element class
class UserCard extends HTMLElement {
constructor() {
super();

// Attach Shadow DOM
this.attachShadow({ mode: "open" });

// Add content
this.shadowRoot.innerHTML = `
<style>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
max-width: 300px;
}
.name { font-weight: bold; font-size: 1.2em; }
.email { color: #666; }
</style>
<div class="card">
<div class="name"></div>
<div class="email"></div>
<slot></slot>
</div>
`;
}

// Called when element is added to DOM
connectedCallback() {
this.render();
}

// Watch for attribute changes
static get observedAttributes() {
return ["name", "email"];
}

// Called when observed attribute changes
attributeChangedCallback(name, oldValue, newValue) {
this.render();
}

render() {
this.shadowRoot.querySelector(".name").textContent =
this.getAttribute("name") || "Unknown";
this.shadowRoot.querySelector(".email").textContent =
this.getAttribute("email") || "";
}
}

// Register the custom element
customElements.define("user-card", UserCard);

Using the Custom Element

<!-- Basic usage -->
<user-card name="John Doe" email="john@example.com"></user-card>

<!-- With slotted content -->
<user-card name="Jane Doe" email="jane@example.com">
<button>View Profile</button>
</user-card>

Lifecycle Callbacks

CallbackWhen Called
constructor()Element created
connectedCallback()Added to DOM
disconnectedCallback()Removed from DOM
attributeChangedCallback()Attribute changed
adoptedCallback()Moved to new document

HTML Templates

<!-- Define template -->
<template id="card-template">
<style>
.card {
border: 1px solid #ccc;
padding: 20px;
}
</style>
<div class="card">
<slot name="title"></slot>
<slot></slot>
</div>
</template>

<script>
class TemplateCard extends HTMLElement {
constructor() {
super();
const template = document.getElementById("card-template");
const content = template.content.cloneNode(true);

this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(content);
}
}

customElements.define("template-card", TemplateCard);
</script>

<!-- Usage -->
<template-card>
<h2 slot="title">Card Title</h2>
<p>Card content goes here</p>
</template-card>

Extending Built-in Elements

// Extend existing HTML element
class FancyButton extends HTMLButtonElement {
constructor() {
super();
this.style.background = "linear-gradient(45deg, #ff6b6b, #4ecdc4)";
this.style.border = "none";
this.style.padding = "10px 20px";
this.style.color = "white";
this.style.borderRadius = "5px";
}
}

customElements.define("fancy-button", FancyButton, { extends: "button" });
<!-- Usage with is attribute -->
<button is="fancy-button">Click Me</button>

Real-World Example

class ToggleSwitch extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
.switch {
width: 50px;
height: 26px;
background: #ccc;
border-radius: 13px;
cursor: pointer;
transition: background 0.3s;
}
.switch.on { background: #4CAF50; }
.slider {
width: 22px;
height: 22px;
background: white;
border-radius: 50%;
margin: 2px;
transition: transform 0.3s;
}
.switch.on .slider { transform: translateX(24px); }
</style>
<div class="switch">
<div class="slider"></div>
</div>
`;

this.shadowRoot
.querySelector(".switch")
.addEventListener("click", () => this.toggle());
}

toggle() {
const isOn = this.hasAttribute("on");
if (isOn) {
this.removeAttribute("on");
} else {
this.setAttribute("on", "");
}
this.shadowRoot.querySelector(".switch").classList.toggle("on");

this.dispatchEvent(
new CustomEvent("change", {
detail: { on: !isOn },
})
);
}
}

customElements.define("toggle-switch", ToggleSwitch);

Key Points

  • Custom Elements: Define new HTML tags
  • Shadow DOM: Style and DOM encapsulation
  • Templates: Reusable markup structures
  • Slots: Content projection
  • Framework-agnostic and native to browsers
  • Works with any JavaScript framework