logo
banner
Photo by JUNHØ on Unsplash

Demystifying Web Components in JavaScript: The Native Way to Build Reusable UI

In the world of frontend development, we’ve been spoiled by frameworks — React, Vue, Angular — all offering component-driven architectures that simplify UI development. But beneath all that magic lies something native and powerful: Web Components.

They're built into the browser, require no build tools, and can bring real modularity and encapsulation — all without tying you to a framework.

If you're a JavaScript developer looking to create highly reusable, framework-agnostic UI components, this post is your practical deep dive into the what, why, and how of Web Components.


🧩 What Are Web Components?

Web Components are a set of native browser APIs that let you define custom HTML elements with their own structure, behavior, and style — all encapsulated and reusable.

They consist of four core technologies:

  1. Custom Elements — Define custom HTML tags with your own behavior.

  2. Shadow DOM — Isolate your component's internal DOM and styles.

  3. HTML Templates — Define chunks of HTML for reuse without rendering them immediately.

  4. ES Modules — (Often used) to load JS modules in a clean, modular way.

Together, they allow you to write components like:

<user-card name="John" role="Software Developer"></user-card>

...and implement the behavior without React, Vue, or even jQuery.


⚙️ Building Blocks of Web Components

Let’s explore the core APIs individually:

1. Custom Elements

You can define your own HTML tags and associate behavior with them via JavaScript.

class MyButton extends HTMLElement { connectedCallback() { this.innerHTML = `<button>Click me</button>`; } } customElements.define('my-button', MyButton);

Usage:

<my-button></my-button>

The lifecycle methods available:

You can also specify which attributes to observe:

static get observedAttributes() { return ['label']; }

2. Shadow DOM

Shadow DOM allows you to encapsulate markup and styles so they don’t leak into or out of your component.

class ShadowGreeting extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: 'open' }); shadow.innerHTML = ` <style> p { color: red; font-weight: bold; } </style> <p>Hello from the Shadow DOM!</p> `; } } customElements.define('shadow-greeting', ShadowGreeting);

Why it's useful:

3. HTML Templates

The <template> tag lets you define reusable markup without rendering it until you need it.

<template id="user-template"> <div class="user"> <p><slot name="name"></slot></p> </div> </template>

And in JS:

class UserCard extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: 'open' }); const template = document.getElementById('user-template').content.cloneNode(true); shadow.appendChild(template); } } customElements.define('user-card', UserCard);

This improves performance and keeps things clean.


🧠 A Practical Example: <fancy-card>

Let’s build a real component step by step.

class FancyCard extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: 'open' }); const wrapper = document.createElement('div'); wrapper.innerHTML = ` <style> .card { padding: 1rem; background: #fff; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border-radius: 8px; font-family: sans-serif; } .title { font-size: 1.25rem; margin-bottom: 0.5rem; } </style> <div class="card"> <div class="title"><slot name="title"></slot></div> <div class="content"><slot></slot></div> </div> `; shadow.appendChild(wrapper); } } customElements.define('fancy-card', FancyCard);

Use it like this:

<fancy-card> <span slot="title">Welcome!</span> <p>This is a beautiful web component.</p> </fancy-card>

⚡ Benefits of Web Components

FeatureDescription
🔄 ReusableDefine once, reuse everywhere
🧩 EncapsulatedCSS and DOM are scoped
🚫 No Framework Lock-inVanilla JS or works within any framework
🔌 Interop FriendlyUse in React, Angular, Vue apps
🔒 StablePart of the web standard, future-proof

🛠️ Real-World Use Cases


❗ Pitfalls & Considerations

While Web Components are powerful, they’re not always the best fit for everything.

Verbose API — Writing in pure JS feels clunky compared to JSX or templates.

State Management — No built-in reactive data flow (yet).

Tooling Ecosystem — Less mature than React/Vue.

SEO — Content in Shadow DOM may not be indexed (depending on crawler).


🧪 Want More Power? Use a Library

If you like the idea of Web Components but hate the boilerplate, use a modern library:

🔥 Lit

import { LitElement, html, css } from 'lit'; class MyLitCard extends LitElement { static styles = css` .card { padding: 1rem; border: 1px solid #ccc; } `; render() { return html`<div class="card"><slot></slot></div>`; } } customElements.define('my-lit-card', MyLitCard);

🛠️ Other Options:


🧘‍♂️ Final Thoughts

Web Components give you freedom — to build once, deploy anywhere, and stop rebuilding the same button in every framework.

They’re:

If you’re building UI libraries, micro frontends, or embeddable widgets — Web Components should be in your toolbox.


📦 TL;DR