Web Components let you create reusable, encapsulated HTML elements that work everywhere — React, Vue, Angular, or vanilla JS.
The Three Pillars
- Custom Elements: Define new HTML tags
- Shadow DOM: Encapsulated DOM and styling
- HTML Templates: Reusable markup
Creating a Custom Element
class UserCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
}
static get observedAttributes() {
return ["name", "avatar", "role"];
}
attributeChangedCallback(name, oldVal, newVal) {
if (oldVal !== newVal) this.render();
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host { display: block; padding: 16px; border-radius: 8px; background: var(--card-bg, #fff); }
.name { font-weight: bold; font-size: 1.1em; }
.role { color: #666; font-size: 0.9em; }
</style>
<img src="${this.getAttribute("avatar")}" width="48" height="48" />
<div class="name">${this.getAttribute("name")}</div>
<div class="role">${this.getAttribute("role")}</div>
`;
}
}
customElements.define("user-card", UserCard);Usage
<!-- HTML -->
<user-card name="Rahul" avatar="/avatar.jpg" role="Engineer"></user-card>
// React
function App() {
return <user-card name="Rahul" avatar="/avatar.jpg" role="Engineer" />;
}
// Vue
<template>
<user-card :name="user.name" :avatar="user.avatar" :role="user.role" />
</template>Shadow DOM Styling
/* From outside: CSS custom properties pierce Shadow DOM */
user-card {
--card-bg: #f0f0f0;
}
/* Inside Shadow DOM: ::part() exposes elements */
// In component: <div part="title">...</div>
// From outside: user-card::part(title) { color: blue; }Slots for Composition
// Component template
<slot name="header">Default Header</slot>
<slot>Default content</slot>
// Usage
<user-card>
<h2 slot="header">Custom Header</h2>
<p>This goes in the default slot</p>
</user-card>When to Use Web Components
- Design system shared across frameworks
- Micro-frontend integration points
- Third-party embeddable widgets
- When you need true style encapsulation