Can Web Components Replace Frontend Frameworks? A Deep Dive

This article examines why modern frontend developers adopt frameworks, outlines their advantages and drawbacks, and then explores Web Components—custom elements, Shadow DOM, and HTML templates—as native alternatives that could potentially replace framework‑based componentization while preserving performance and compatibility.

ELab Team
ELab Team
ELab Team
Can Web Components Replace Frontend Frameworks? A Deep Dive

Why Use Frameworks?

Understanding Frameworks

As modern frontend developers, embracing a framework is essential for survival; some developers dive straight into frameworks, while others have witnessed the evolution from native JavaScript to jQuery and the current dominance of Vue, React, and Angular in China.

Using a framework efficiently also requires a certain level of understanding of the framework itself, which helps us learn, comprehend, and apply it better. The table below (derived from Evan You’s comparison of the three major frameworks) can improve our perception of them.

Framework Advantages

Based on the differences among the frameworks, we see that each has its own design, development path, and ecosystem, all stemming from how they define their responsibility scope. Despite differences, they share common goals: higher efficiency, better performance, and smoother cross‑browser differences.

Frameworks raise the lower bound of development speed and baseline performance, allowing developers to balance rapid development with acceptable performance. For React and Vue, advantages include data binding (one‑way/two‑way), component‑based development (hooks, lifecycle, scoped isolation), virtual DOM (diff algorithm) and routing, among others.

These advantages rely on native JavaScript syntax and browser support; browsers provide the underlying APIs, while frameworks build higher‑level abstractions on top of them.

However, frameworks also bring two drawbacks:

Performance loss – native DOM manipulation can sometimes be faster than framework abstractions (e.g., React vs. plain JS DOM handling). The following images compare React and native JS performance on desktop, tablet, and mobile Chrome.

Environment isolation – components built with one framework may not integrate smoothly with another (e.g., Vue component libraries in a React project).

If native APIs can provide some of these functionalities, we might replace parts of a framework while retaining its benefits and avoiding performance penalties.

Understanding Web Components

Web Component

Web Components are a set of native browser APIs and templates that enable custom, reusable UI elements without relying on a framework. They address code reuse, custom element definition, and encapsulated styling.

The advantages discussed earlier (e.g., componentization) already exist in native form through Web Components, but fully replacing frameworks requires the ecosystem (routing, state management, performance optimizations) that frameworks provide.

Can Native Componentization Replace Framework Componentization?

Component characteristics include high cohesion, low coupling, clear markup, and extensible interfaces. Frameworks offer efficient reuse, scoped styles, custom development, lifecycle hooks, and more.

Web Components provide:

Custom Elements : define autonomous or built‑in elements with their own lifecycle and attribute observation.

Shadow DOM : encapsulate markup, style, and behavior, preventing global CSS leakage.

HTML Templates and Slots : enable content distribution and reuse.

Therefore, from a theoretical standpoint, Web Components can replace framework componentization, and Vue 3.2 already introduces partial support for them.

Compatibility

Check browser support on caniuse.com . The following screenshots show support for Custom Elements, Shadow DOM, and HTML Templates.

Custom Elements Declaration and Usage

window.customElements.define

This method registers a custom element globally. It takes three parameters:

name – the tag name (must contain a hyphen).

constructor – a class extending HTMLElement (or a built‑in element class for custom built‑in elements).

options – optional object, used only for custom built‑in elements (contains extends).

Declaration example:

// autonomous custom element
class CustomEle extends HTMLElement {
  constructor() {
    super();
    // ...
  }
}
customElements.define('custom-ele', CustomEle);

// custom built‑in element extending <div>
class CustomEleBuiltIn extends HTMLDivElement {
  constructor() {
    super();
    // ...
  }
}
customElements.define('custom-ele-build-in', CustomEleBuiltIn, { extends: 'div' });

Usage can be via document.createElement or directly in HTML:

// create and append
const el = document.createElement('custom-ele');
el.setAttribute('img', 'https://example.com/img.png');
el.setAttribute('text', 'Hover description');
document.querySelector('#app').appendChild(el);

// HTML markup
<custom-ele img="https://example.com/img.png" text="Hover description"></custom-ele>

// custom built‑in element
const div = document.createElement('div', { is: 'custom-ele-build-in' });

window.customElements.get

Returns the constructor for a registered custom element.

const ctorBefore = customElements.get('custom-ele'); // undefined
customElements.define('custom-ele', CustomEle);
const ctorAfter = customElements.get('custom-ele'); // CustomEle

window.customElements.upgrade

Upgrades a custom element that was created before its definition.

const el = document.createElement('custom-ele');
// later define
customElements.define('custom-ele', CustomEle);
customElements.upgrade(el);

window.customElements.whenDefined

Returns a promise that resolves when the custom element is defined.

customElements.whenDefined('custom-ele').then(() => {
  console.log('custom-ele defined');
});

Custom Element Lifecycle

constructor – runs when the element is instantiated.

connectedCallback – runs when the element is added to the document.

attributeChangedCallback – runs when observed attributes change.

adoptedCallback – runs when the element is moved to a new document.

disconnectedCallback – runs when the element is removed from the document.

Example with all callbacks:

class CustomEle extends HTMLElement {
  constructor() {
    super();
    console.log('constructor executed');
  }
  connectedCallback() {
    console.log('connectedCallback executed');
  }
  static get observedAttributes() { return ['img', 'text']; }
  attributeChangedCallback(name, oldValue, newValue) {
    console.log('attributeChangedCallback', name);
    if (name === 'img') {
      this.shadowRoot.querySelector('img').src = this.getAttribute('img');
    }
    if (name === 'text') {
      this.shadowRoot.querySelector('.info').textContent = this.getAttribute('text');
    }
  }
  adoptedCallback() { console.log('adoptedCallback executed'); }
  disconnectedCallback() { console.log('disconnectedCallback executed'); }
}
customElements.define('custom-ele', CustomEle);

Shadow DOM Usage

Shadow DOM encapsulates markup, style, and behavior, isolating them from the rest of the page.

attachShadow – attaches a shadow root to an element. The mode option can be open (accessible via element.shadowRoot) or closed (inaccessible).

Example:

class CustomEle extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    const wrapper = document.createElement('span');
    wrapper.className = 'wrapper';
    const icon = document.createElement('span');
    icon.className = 'icon';
    const info = document.createElement('span');
    info.className = 'info';
    const img = document.createElement('img');
    img.src = this.getAttribute('img');
    icon.appendChild(img);
    const style = document.createElement('style');
    style.textContent = `
      .wrapper { position: relative; }
      .info { font-size: 0.8rem; width: 200px; display: inline-block; border: 1px solid black; padding: 10px; background: white; border-radius: 10px; opacity: 0; transition: 0.6s all; position: absolute; bottom: 20px; left: 10px; z-index: 3; }
      img { width: 1.2rem; }
      .icon:hover + .info, .icon:focus + .info { opacity: 1; }
    `;
    shadow.appendChild(style);
    shadow.appendChild(wrapper);
    wrapper.appendChild(icon);
    wrapper.appendChild(info);
  }
}
customElements.define('custom-ele', CustomEle);

If you prefer an external stylesheet, replace the style element with a link element (note possible FOUC).

Template

template – content inside a <template> is not rendered but can be cloned via JavaScript for reuse.

Example:

class CustomEle extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    const tmpl = document.getElementById('my-paragraph');
    if (tmpl) {
      shadow.appendChild(tmpl.content.cloneNode(true));
    }
  }
}
customElements.define('custom-ele', CustomEle);

// HTML
<template id="my-paragraph">
  <style>p { color: white; background: #666; padding: 5px; }</style>
  <p>My paragraph</p>
</template>

Slot

slot – placeholder inside a shadow DOM that can be filled with external markup. slotchange event fires when assigned nodes change.

Example with named slots:

class CustomEle extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    const tmpl = document.getElementById('my-paragraph');
    if (tmpl) {
      shadow.appendChild(tmpl.content.cloneNode(true));
    }
    const slot2 = document.createElement('slot');
    slot2.name = 'newText2';
    shadow.appendChild(slot2);
    const slots = shadow.querySelectorAll('slot');
    slots.forEach(slot => {
      slot.addEventListener('slotchange', e => {
        console.log('slotchange', slot.name, e);
      });
    });
  }
}
customElements.define('custom-ele', CustomEle);

<!-- Usage -->
<custom-ele>
  <p slot="newText1">newText1</p>
  <p slot="newText2">newText2</p>
</custom-ele>

Related Libraries and Sites

webcomponents.org – examples, tutorials, and information.

Hybrids – lightweight library favoring plain objects and functions.

Polymer – Google’s framework with polyfills and utilities.

Snuggsi.es – tiny library for Web Components (includes polyfill).

Slim.js – high‑performance component library.

Smart.js – simple API for cross‑browser custom elements.

Stencil – toolchain for building reusable design systems with Web Components.

References

https://developer.mozilla.org/zh-CN/docs/Web/Web_Components

https://medium.com/jspoint/the-anatomy-of-web-components-d6afedb81b37

https://www.ruanyifeng.com/blog/2019/08/web_components.html

https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements

https://developers.google.cn/web/fundamentals/web-components

https://objectpartners.com/2015/11/19/comparing-react-js-performance-vs-native-dom/

https://bugs.webkit.org/show_bug.cgi?id=182671

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptcustom elementsfrontend frameworks
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.