Web Components: A Modern Guide
Author framework-free reusable elements with custom elements, shadow DOM, and slots.
Define a Custom Element
``js
class StarRating extends HTMLElement {
static observedAttributes = ['value'];
connectedCallback() { this.render(); }
attributeChangedCallback() { this.render(); }
render() {
const v = +this.getAttribute('value') || 0;
this.innerHTML = '★'.repeat(v) + '☆'.repeat(5 - v);
}
}
customElements.define('star-rating', StarRating);
`
Use anywhere: . Works in React, Vue, vanilla HTML.
Shadow DOM
Encapsulates styles and DOM:
`js
this.attachShadow({ mode: 'open' }).innerHTML =
;
`
Outside CSS does not leak in; :host styles the element itself.
Slots and Composition
` Body contenthtml
`
Inside the component, and a default project content.
Form-Associated Custom Elements
ElementInternals (broadly shipped by 2026) lets custom elements participate in forms:
`js
static formAssociated = true;
constructor() { super(); this._internals = this.attachInternals(); }
`
The element submits with the form, supports validation, and integrates with FormData.
CSS Parts
Expose styling hooks without breaking encapsulation:
`html
`
Consumers style via my-comp::part(trigger) { color: red; }.
Declarative Shadow DOM
Server-rendered shadow DOM via `. Eliminates the JS-required problem for SSR.
Lit and Friends
Hand-rolled Web Components are verbose. Lit (3KB) gives you reactive properties and templates without the framework lock-in.
When to Use
- Design system primitives (Buttons, Modals, Tooltips) - Embeddable widgets across multiple host frameworks - Long-lived components (5+ year horizon)
For testing patterns see [testing with vitest 2026](/blog/testing-with-vitest-2026).