Web Components
What are Web Components?
Web Components are browser-native APIs for creating reusable, encapsulated HTML elements — no framework required. They work in all modern browsers and can be used inside React, Vue, Angular, or plain HTML.
Three core technologies:
- Custom Elements — define new HTML tags with lifecycle callbacks
- Shadow DOM — encapsulate styles and markup from the page
- HTML Templates — inert, reusable markup fragments
Custom Elements
<script>
class GreetingCard extends HTMLElement {
static observedAttributes = ['name'];
connectedCallback() {
this.render();
}
attributeChangedCallback(name, oldVal, newVal) {
if (oldVal !== newVal) this.render();
}
render() {
const name = this.getAttribute('name') || 'World';
this.textContent = '';
const p = document.createElement('p');
p.textContent = `Hello, ${name}!`;
this.appendChild(p);
}
}
customElements.define('greeting-card', GreetingCard);
</script>
<greeting-card name="Alice"></greeting-card>
Lifecycle Callbacks
| Callback | When |
|---|---|
connectedCallback |
Element added to DOM |
disconnectedCallback |
Element removed |
attributeChangedCallback |
Observed attribute changes |
adoptedCallback |
Element moved to new document |
Register with customElements.define('tag-name', Class). Tag names must contain a hyphen.
Shadow DOM
Styles inside Shadow DOM don’t leak out; page styles don’t leak in:
class UserBadge extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
span {
background: #007bff;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.875rem;
}
</style>
<span><slot></slot></span>
`;
}
}
customElements.define('user-badge', UserBadge);
<user-badge>Admin</user-badge>
mode: 'open'—element.shadowRootaccessible (recommended for debugging)mode: 'closed'— shadow root hidden from outside JS
HTML Templates
Templates are inert until cloned — ideal for repeated structures:
<template id="card-template">
<div class="card">
<h3 class="title"></h3>
<p class="body"></p>
</div>
</template>
<script>
const template = document.getElementById('card-template');
const clone = template.content.cloneNode(true);
clone.querySelector('.title').textContent = 'Hello';
clone.querySelector('.body').textContent = 'World';
document.body.appendChild(clone);
</script>
Slots — Content Projection
shadow.innerHTML = `
<style>.wrapper { border: 1px solid #ccc; padding: 1rem; }</style>
<div class="wrapper">
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
`;
<my-card>
<h2 slot="header">Title</h2>
<p>Body content</p>
<small slot="footer">Updated today</small>
</my-card>
Unnamed <slot> is the default slot. Use ::slotted(p) in CSS to style slotted content (limited styling).
Attributes and Properties
class ToggleButton extends HTMLElement {
static get observedAttributes() { return ['pressed']; }
get pressed() {
return this.hasAttribute('pressed');
}
set pressed(val) {
val ? this.setAttribute('pressed', '') : this.removeAttribute('pressed');
}
}
Reflect important state to attributes for CSS styling: [pressed] { background: green; }.
When to Use Web Components
| Good fit | Less ideal |
|---|---|
| Design systems shared across frameworks | Simple static pages |
| Embeddable widgets (chat, payment) | App entirely in one framework |
| Browser extensions | Need SSR without hydration setup |
| Long-lived component libraries | Rapid prototyping |
Frameworks like Lit, Shoelace, and FAST simplify reactive properties and efficient rendering.
Best Practices
- Use Shadow DOM for style encapsulation in design systems
- Observed attributes for declarative API (
<tabs selected="2">) - Progressive enhancement — component works without JS where possible
- Accessible by default — keyboard support, ARIA, focus management
- Lazy define —
customElements.definebefore first use, or usecustomElements.whenDefined()
Troubleshooting
Component not rendering
- Ensure class extends
HTMLElementandsuper()is called in constructor - Tag name must include a hyphen; define before using in HTML
Styles not applying inside component
- Styles in Shadow DOM don’t affect light DOM; put component styles inside shadow
<style>
Form elements not participating in forms
- Use
ElementInternalsandformAssociatedfor form-aware custom elements
Web Components are the web platform’s answer to reusable UI — learn them for cross-framework design systems and embeddable widgets.