Web Components Basics
Create reusable custom HTML elements with Web Components.
By EMEPublished: February 20, 2025
web componentscustom elementsshadow domslots
A Simple Analogy
Web Components are like LEGO bricks. Self-contained, reusable pieces that work together without conflict.
Why Web Components?
- Reusable: Use across projects
- Encapsulation: Isolated styles/scripts
- Framework-agnostic: Work with any framework
- Standard: Native browser support
- Composable: Build complex UIs
Custom Element
class MyCounter extends HTMLElement {
constructor() {
super();
this.count = 0;
}
connectedCallback() {
this.render();
this.querySelector('button').addEventListener('click', () => {
this.count++;
this.render();
});
}
render() {
this.innerHTML = `
<p>Count: ${this.count}</p>
<button>Increment</button>
`;
}
}
customElements.define('my-counter', MyCounter);
<my-counter></my-counter>
Shadow DOM
class MyCard extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
.card { border: 1px solid #ccc; padding: 16px; }
h2 { margin: 0; color: #333; }
</style>
<div class="card">
<h2>My Card</h2>
<slot></slot>
</div>
`;
}
}
customElements.define('my-card', MyCard);
<my-card>
<p>This content goes in the slot</p>
</my-card>
Slots (Content Projection)
class MyLayout extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
.container { display: flex; gap: 16px; }
.header { background: #f0f0f0; padding: 16px; }
.content { flex: 1; }
</style>
<div class="container">
<div class="header">
<slot name="header"></slot>
</div>
<div class="content">
<slot></slot>
</div>
</div>
`;
}
}
customElements.define('my-layout', MyLayout);
<my-layout>
<h1 slot="header">Welcome</h1>
<p>Main content here</p>
</my-layout>
Attributes & Properties
class MyButton extends HTMLElement {
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(value) {
if (value) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
connectedCallback() {
this.update();
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'disabled') {
this.update();
}
}
static get observedAttributes() {
return ['disabled'];
}
update() {
this.style.opacity = this.disabled ? '0.5' : '1';
}
}
customElements.define('my-button', MyButton);
Best Practices
- Semantic: Use meaningful element names (dash required)
- Encapsulation: Use Shadow DOM for style isolation
- Documentation: Document attributes and slots
- Accessibility: Include ARIA labels
- Performance: Lazy load templates
Related Concepts
- Custom Elements v1
- Template element
- Lit library (helpers)
- Framework integration
Summary
Create reusable custom HTML elements with Shadow DOM for encapsulation and slots for flexible content projection.