Every major framework eventually reinvents the same idea — a reusable, encapsulated UI unit — and Web Components is the browser's own native answer to that idea, standardized rather than framework-specific.
Web Components is a set of browser-native APIs — Custom Elements, Shadow DOM, and HTML Templates — for building reusable, encapsulated UI components that work in any framework or no framework at all. A custom element you define once can be used in a React app, a Vue app, or plain HTML, since it's just a standard DOM element as far as the browser is concerned.
Why Web Components Matter (and When to Skip Them)
Framework-agnostic reusability is the core value proposition — a component built as a Web Component can be shared across teams using different frameworks (or no framework), without the "framework lock-in" a React or Vue component library carries. This matters most for design systems and widgets that need to work across multiple applications with different tech stacks.
Skip Web Components for a typical application built entirely within one framework — React, Vue, or Svelte's own component models are more ergonomic and better-integrated with their respective ecosystems (state management, dev tools, testing) than the lower-level Web Components APIs, and the framework-agnostic benefit doesn't matter if you're not actually crossing framework boundaries.
Getting Started with Web Components
Defining a custom element with Shadow DOM encapsulation:
class MyCounter extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.count = 0;
}
connectedCallback() {
this.render();
this.shadowRoot.querySelector("button").addEventListener("click", () => {
this.count++;
this.render();
});
}
render() {
this.shadowRoot.innerHTML = `
<style>button { padding: 0.5rem 1rem; }</style>
<button>Count: ${this.count}</button>
`;
}
}
customElements.define("my-counter", MyCounter);
Using it anywhere, no framework required:
<my-counter></my-counter>
Core Web Components Concepts Every Developer Should Know
Custom Elements let you define new HTML tags with their own behavior, registered via customElements.define() — once registered, the browser treats them like any built-in element, usable directly in HTML markup without any framework runtime.
Shadow DOM provides genuine style and DOM encapsulation. Styles defined inside a shadow root don't leak out, and outside styles don't leak in by default — this is real, browser-enforced isolation, stronger than the scoped-styles conventions frameworks like Vue and Svelte implement at the tooling level.
HTML Templates (<template>) define reusable markup fragments that aren't rendered until explicitly cloned into the DOM via JavaScript — useful for defining a component's structure without it appearing in the page until instantiated.
Lifecycle callbacks (connectedCallback, disconnectedCallback, attributeChangedCallback) hook into the element's DOM lifecycle, roughly analogous to component lifecycle methods in frameworks but operating at the native DOM level rather than a framework's virtual layer.
Common Web Components Mistakes and How to Fix Them
Mistake 1: using Web Components for an entire application instead of a framework, missing out on the ergonomics, state management, and developer tooling frameworks provide for full application development. Fix: recognize Web Components' strength is component-level reusability across boundaries, not full application architecture.
Mistake 2: not handling attribute-to-property synchronization carefully, leading to inconsistent behavior when an element's attributes and JavaScript properties get out of sync. Fix: implement attributeChangedCallback and property getters/setters consistently, or use a library (Lit) that handles this synchronization for you.
Mistake 3: skipping a helper library like Lit and hand-rolling everything with raw Custom Elements APIs, resulting in significantly more boilerplate than necessary. Fix: use Lit or a similar lightweight library for real Web Components work — it handles reactivity, templating, and lifecycle boilerplate while still producing standard Web Components.
When Should You Use Web Components Instead of Framework Components?
Use Web Components when building a design system, widget, or component library that genuinely needs to work across multiple frameworks or in framework-agnostic contexts (embedding in third-party sites, for instance). Use your framework's native component model for typical application development within a single framework, where the cross-framework portability isn't actually needed and the framework's own tooling is more ergonomic.
Web Components in Production
Use Lit (or a similar library) rather than raw Custom Elements APIs for anything beyond a trivial example, since it substantially reduces boilerplate while still producing standards-compliant Web Components. Also test actual integration in each target framework (React's Web Components interop has historically had some rough edges around complex props/events) rather than assuming universal compatibility without verification.
If you're building a component that genuinely needs to be shared across React, Vue, and vanilla HTML contexts, Web Components is the tool built specifically for that cross-framework portability requirement.