Web components are the platform's answer to reusable UI
Web components are the browser’s own answer to “reusable UI component,” and they
predate needing a framework for it. Three platform features combine: custom
elements (define your own HTML tag with behaviour), shadow DOM (encapsulate its
markup and styles so nothing leaks in or out), and templates (declare reusable
markup). Together they let you ship a <my-widget> that any page — React, Vue,
Angular, or plain HTML — can use, because the browser understands it natively. They
will not replace your framework for building a whole app, but for cross-framework,
long-lived UI — a design system consumed by many teams on many stacks — they are
exactly the right tool, precisely because they belong to the platform and not to a
framework that will change.
A custom element is a tag the browser understands
You extend HTMLElement, define lifecycle callbacks, and register a tag name. From
then on <my-button> works in any HTML, no import, no framework:
class MyButton extends HTMLElement {
connectedCallback() { // lifecycle: when it enters the DOM
const shadow = this.attachShadow({ mode: "open" }); // encapsulation boundary
shadow.innerHTML = `
<style>button { background: var(--brand, #fe854c); }</style>
<button><slot></slot></button>`; // <slot> projects the consumer's content
}
}
customElements.define("my-button", MyButton); // now <my-button> is a real tag
The shadow DOM is the key move: styles inside it do not leak out, and the page’s CSS does not leak in, so the component looks the same wherever it is dropped.
Style seams: parts and custom properties
Encapsulation would be a prison if consumers could not theme the component, so the
platform provides deliberate seams. var(--brand) above lets a consumer set a custom
property that pierces the shadow boundary; ::part() lets them style specific
internals you expose:
/* the consuming page themes the encapsulated component through the seams you allow */
my-button { --brand: #157878; } /* custom property crosses the boundary */
my-button::part(label) { font-weight: 700; } /* style a part the element exposed */
You choose exactly what is themeable — encapsulation by default, customization by opt-in.
Where web components fit, and where they don’t
Be honest about the trade. Web components are the right tool for a design system or widget shared across frameworks and meant to outlive any one of them — a component library a big org’s many stacks all consume, an embeddable widget, anything that must work in a page you do not control. They are a poor fit for building a whole application’s view layer: they lack the ergonomics, state management, and rich ecosystem a framework gives, and reinventing those on top of raw custom elements is a lot of work (which is why Lit and similar exist to smooth it). So the pattern is usually web components for the shared, long-lived, cross-framework pieces; a framework for the app that assembles them. Their superpower is belonging to the platform, which means they cannot be deprecated by a framework’s next major version. The theme-toggle and loading-button-atom exercises are natural web-component shapes — small, reusable, themeable through the seams — and good places to feel where the platform’s own components shine.