Web Components
Build framework-agnostic UI with Web Components - Custom Elements, Shadow DOM encapsulation, HTML Templates, and the Lit library.
skill: Web Components category: Frontend tags: [Web Components, Custom Elements, Shadow DOM, HTML Templates, Lit] readingTime: 5
Web Components are native browser primitives - they work in any framework (React, Vue, Angular) and without any framework. Shadow DOM provides true CSS encapsulation. Use raw Web Components for design system tokens shared across frameworks; use Lit for anything with reactive state.
Workflow
- Create a Custom ElementCustom Elements extend HTMLElement - the browser treats them like native elements. Lifecycle callbacks (connectedCallback, disconnectedCallback, attributeChangedCallback) manage element state.
- Use HTML templates and slotsHTML <template> elements aren't rendered until cloned - efficient for repeated patterns. Slots project external content into Shadow DOM.
- Build with Lit for reactive componentsLit is a lightweight library (5KB) built on Web Components - adds reactive properties, templates with html tagged literals, and efficient updates without a virtual DOM.
- Use Web Components in React and VueWeb Components are standard HTML - any framework that renders HTML can use them. React 19 added native Web Component support.
- Dispatch and handle Custom EventsCustom Elements communicate with parent components through custom DOM events - a standard, framework-agnostic API for component communication.
Output Format
Web Components: Custom Element with Shadow DOM, HTML templates and slots, Lit for reactive properties, React/Vue consumption, and CustomEvent with composed: true.
class MyEl extends HTMLElement {
static observedAttributes = []
connectedCallback() { this.attachShadow({mode:"open"}); this.#render(); }
attributeChangedCallback() { this.#render(); }
}
customElements.define("my-el", MyEl);
Lit: @property, html`...`, @click=${handler}Examples
Input
Output
Edge Cases & Gotchas
- Custom element name requires hyphen - All custom element names must contain a hyphen (my-element, app-card) to distinguish them from future HTML elements. Single-word names throw a DOMException.
- Shadow DOM and global CSS - CSS custom properties (variables) pierce Shadow DOM boundaries. Stylesheets from outside don't - this is the encapsulation feature, not a bug. Use CSS custom properties for theming.
- Server-side rendering - Web Components are client-side only - they require JavaScript and the browser DOM. For SSR, use Declarative Shadow DOM (HTML attribute shadowrootmode="open") - supported in Chrome 111+.
- Form association - Custom elements don't participate in form submission by default. Use the FormAssociated API (attachInternals(), setFormValue()) to make them work with <form> elements.
When to Break These Rules
- Use React or Vue components within a single framework project - Web Components add boilerplate compared to framework components when you're not crossing framework boundaries.
- Use design tokens (CSS custom properties) without Web Components when you just need shared styling - not all design system elements need to be components.
Reference Files
https://developer.mozilla.org/en-US/docs/Web/API/Web_componentshttps://lit.dev/