Understanding Web Components: Core Concepts, Custom Elements, Shadow DOM, Slots, and Templates
This article introduces Web Components—a W3C‑standardized technology supported by all major browsers—explaining its core concepts (Custom Elements, Shadow DOM, Templates, Slots), providing step‑by‑step code examples, discussing lifecycle callbacks, compatibility, polyfills, and how it compares with frameworks like React and Vue.
The rapid evolution of web technologies has produced many new concepts and frameworks, especially in frontend development; Vue 3 and Vite are popular, but the older yet powerful Web Components deserve attention because they enable framework‑agnostic, reusable custom elements. Web Components are a set of W3C‑standardized APIs (Custom Elements, Shadow DOM, HTML Templates, Slots) that are now natively supported by Chrome, Firefox, Safari, and Edge. Vue 3 even provides native support for Web Components, and many interview questions now focus on Shadow DOM. Web Components Core Concepts Web Components allow developers to create reusable custom elements that work in any browser supporting the standard. Key concepts include: Custom Elements : define new HTML tags with custom behavior and styling. Shadow DOM : encapsulate a component’s internal structure and styles, preventing global namespace pollution. Templates : define reusable HTML structures that can be instantiated multiple times. Slots : placeholders that let external content be inserted into a component. The following sections demonstrate how to build reusable custom elements using these concepts. Custom Elements (自定义元素) Custom Elements let you package HTML into a new tag. Below is a simple button example. Creating a Custom Element First, define the element with customElements.define() and give it a name such as my-button . // main.js class MyButton extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = ` Click Me! `; } } customElements.define('my-button', MyButton); Use the element directly in HTML: Web Components Example The button renders with the defined style. Custom Elements also support behavior such as event listeners. const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.querySelector('button').addEventListener('click', () => { alert('Button clicked!'); }); Lifecycle Callback Methods Custom Elements provide lifecycle callbacks that are invoked at different stages of an element’s existence. constructor() : called when the element instance is created; good for initializing state and attaching Shadow DOM. connectedCallback() : called when the element is inserted into the DOM; useful for fetching data or rendering content. disconnectedCallback() : called when the element is removed from the DOM; useful for cleanup. attributeChangedCallback(name, oldValue, newValue) : called when observed attributes change; requires a static observedAttributes array. Example implementation: class MyCustomElement extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = ' This is a custom element '; } connectedCallback() { console.log('Custom element connected to the DOM'); } disconnectedCallback() { console.log('Custom element disconnected from the DOM'); } attributeChangedCallback(name, oldValue, newValue) { console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`); } static get observedAttributes() { return ['my-attribute']; } } customElements.define('my-custom-element', MyCustomElement); Use it in HTML with an observed attribute: Shadow DOM (影子 DOM) Shadow DOM creates an isolated DOM subtree whose styles and markup do not affect the main document and vice versa. Using innerHTML class MyElementInnerHTML extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = ` Using innerHTML `; } } customElements.define('my-element-inner', MyElementInnerHTML); Using createElement and appendChild const wrapper = document.createElement('p'); wrapper.textContent = 'Using createElement and appendChild'; const style = document.createElement('style'); style.textContent = `p { color: gray; }`; class MyElementAppend extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.appendChild(wrapper); shadowRoot.appendChild(style); } } customElements.define('my-element-append', MyElementAppend); Shadow Mode When creating a Shadow DOM you can specify mode: 'open' or mode: 'closed' . In open mode the shadow root is accessible via element.shadowRoot ; in closed mode it returns null . class OpenMyElement extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = ` This is an open mode Shadow DOM `; } } customElements.define('open-my-element', OpenMyElement); // Access from outside const el = document.querySelector('open-my-element'); console.log(el.shadowRoot); // ShadowRoot object class ClosedMyElement extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'closed' }); shadowRoot.innerHTML = ` This is a closed mode Shadow DOM `; } } customElements.define('closed-my-element', ClosedMyElement); // Attempt to access const el = document.querySelector('closed-my-element'); console.log(el.shadowRoot); // null Slots (插槽) Slots allow external content to be projected into a component, increasing flexibility. Basic Slot Usage class MyButton extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = ` Click Me! `; } } customElements.define('my-button', MyButton); Now any content placed inside <my-button> replaces the default slot: Custom Text Named Slots class MyButtonName extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = ` `; } } customElements.define('my-button-name', MyButtonName); John Doe 30 [email protected] Templates (模板) Templates let you define reusable HTML fragments that can be cloned into a Shadow DOM. class MyButton extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); const template = document.getElementById('my-button-template'); shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('my-button', MyButton); Related Extensions Compatibility Most modern browsers now natively support Custom Elements, Shadow DOM, and HTML Templates, but older browsers may require polyfills. Polyfills Use @webcomponents/webcomponentsjs to add support for browsers that lack native implementations. npm install @webcomponents/webcomponentsjs Comparison with React and Vue Web Components are framework‑agnostic, lightweight, and provide true encapsulation via Shadow DOM, making them ideal for component libraries. However, they lack the rich ecosystem, state‑management, routing, and performance optimizations (e.g., virtual DOM) that frameworks like React and Vue offer. Real‑World Use Cases Vue 3 can compile Vue components to Web Components. MicroApp – a micro‑frontend framework built on Web Components. Twitter switched embedded tweets from iframes to Shadow DOM for better performance. Svelte + Vite can quickly scaffold Web Component projects. Polymer provides tooling to simplify custom element creation. References MDN Web Docs – Web Components Introduction "You Don't Know Web Components" – current status Custom Elements v1 – reusable web components Web Components Tutorial for Beginners (2019) Conclusion Web Components, driven by the W3C, enable developers to create standard, reusable UI elements with encapsulated markup, style, and behavior. Their adoption across many projects demonstrates their value, and understanding them helps bridge the gap between framework‑specific components and native web standards. Hope this article helps you; feel free to discuss in the comments.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.