Web Development
Web Components and Shadow DOM
GitHub's entire UI component library is built with Web Components and ships without a JavaScript framework as a dependency. Salesforce's Lightning Web Components power every page of the Salesforce platform, serving 150,000+ enterprise customers. Web Components are not an experimental API - they are a stable W3C standard with 96% browser support.
- GitHub replaced their custom JavaScript UI widgets with Web Components (github/github-elements) in 2019-2022. The components ship in the github.com HTML and load in under 200ms on first visit because no framework bundle is needed.
- Adobe's Spectrum Web Components implement their design system in framework-agnostic Web Components. The same components are used in React, Vue, and Angular apps inside Adobe Creative Cloud products.
- Google's Material Web (material-web) - the official Material Design 3 implementation - ships as Web Components with zero framework dependencies, used across Google Search, Gmail, and Workspace.
Custom Elements
A Custom Element is a class that extends HTMLElement and is registered with a unique tag name containing a hyphen. The browser calls lifecycle callbacks automatically: connectedCallback when the element is inserted into the DOM, disconnectedCallback on removal, and attributeChangedCallback when an observed attribute changes. The element is usable in any HTML, React JSX, Vue template, or JavaScript.
Custom Elements are part of the HTML spec. React 19 added full Custom Element support - event handlers and complex objects now pass through as props without the attribute serialization workarounds needed in React 17-18. Vue 3 and Angular have supported Custom Elements natively since their respective launches.
What naming constraint is required for Custom Element tag names?
Shadow DOM
Shadow DOM attaches an encapsulated DOM tree to a host element. Styles defined inside the shadow root do not leak to the page; styles on the page do not reach inside the shadow root (with the exception of inherited properties like color and font-family). This encapsulation is what makes Web Components truly reusable - a component's internal styles cannot be accidentally overridden by a CSS reset or a global stylesheet.
CSS custom properties (variables) do pierce the Shadow DOM boundary. This is the standard pattern for theming Web Components: the component reads --primary-color from the host document, and teams control the design system by setting variables on :root without needing direct access to shadow internals.
What does Shadow DOM encapsulation guarantee about styles?
Slots
Slots are named insertion points in a Shadow DOM template where the element's consumer can inject their own HTML. A default slot (`<slot>`) accepts all content without a name attribute. Named slots (`<slot name="footer">`) accept only children with the matching `slot="footer"` attribute. This composability is what makes Web Components work like standard HTML elements: `<details>` has implicit slots for `<summary>` and its body content.
Slotted content lives in the light DOM (the host document), not inside the shadow root. This means it is accessible to screen readers, can be styled by the page's CSS, and is visible in browser DevTools as regular DOM. The Shadow DOM composites the slot content into the shadow template visually without moving the nodes.
Where does slotted content live in the DOM?
Lit
Lit (Google, 2018 as LitElement, renamed 2021) is a minimal library built on top of Web Components APIs. It adds reactive properties with automatic re-render on change, tagged template literals for HTML templates with efficient dirty-checking, and TypeScript decorators for concise property declarations. The library is 5 KB compressed - smaller than most utility libraries.
Lit's template literal syntax `html\`...\`` uses the browser's native HTML parser internally and only updates the parts of the DOM that changed - no Virtual DOM. Google uses Lit for the Google Earth Web UI, Google Nest thermostat interface, and YouTube's web player controls.
Web Components require a build step and a framework like Angular or React to use
Web Components are a browser-native API - a custom element can be defined with a plain JS file and used in raw HTML with no build tooling
Lit and other libraries add developer ergonomics (reactive properties, decorators) that benefit from a build step, but the underlying Custom Elements and Shadow DOM APIs are standard browser features available in a `<script>` tag.
What is the primary advantage of Lit over raw Custom Elements with HTMLElement?
Key Ideas
- **Custom Elements:** define a new HTML tag with lifecycle callbacks (connectedCallback, disconnectedCallback, attributeChangedCallback) that the browser handles natively
- **Shadow DOM:** an encapsulated DOM subtree attached to a custom element - styles and JavaScript inside cannot leak out, and external styles cannot reach in
- **Slots:** named and default slots let the consumer inject content into a Web Component's Shadow DOM template at designated insertion points
- **Lit:** Google's minimal library (5 KB) that adds reactive properties, declarative templates, and TypeScript decorators on top of the raw Web Components API
Related Topics
Web Components intersect with framework boundaries:
- DOM and Browser APIs — Custom Elements extend HTMLElement - a deep understanding of the DOM lifecycle is prerequisite
- Micro-frontends — Web Components are the most framework-neutral integration point for micro-frontend architectures
- Vue and Angular: A Comparison — Angular Elements exports Angular components as Web Components; Vue 3 defineCustomElement does the same
Вопросы для размышления
- Shadow DOM encapsulates styles but CSS custom properties pierce the boundary. What is the design implication: should a Web Component design system expose dozens of CSS variables for fine control, or few variables with broad effect?
- Slotted content lives in the light DOM and is styled by the host page. If a design system ships a Web Component card with a default slot, what prevents a consumer from breaking the card's layout by injecting unexpected HTML?
- Lit is 5 KB. React is 42 KB. For a design system used inside React, Angular, and Vue apps simultaneously, does the size comparison matter - or does the complexity of two rendering engines matter more?