Skip to the content.

Web Components

Information

Web Components is a suite of browser standards that allow developers to create reusable, encapsulated HTML elements without any framework dependency. The four core standards are:

Web Components work in all modern browsers natively (Chrome, Firefox, Safari, Edge). No framework is required. They can be used alongside React, Angular, Vue, or with no framework at all.

Installation

No installation is required for browsers that support Web Components natively. To check support:

if ('customElements' in window) {
    console.log('Custom Elements supported');
}

For a lightweight helper library:

# Lit — simplifies writing Web Components
npm install lit

For polyfills targeting older browsers:

npm install @webcomponents/webcomponentsjs

Configuration

Web Components need no global configuration. Each component is self-contained. The recommended way to distribute components is as ES module files loaded via <script type="module">.

For npm-distributed components, set "type": "module" in package.json and expose the component file as the main entry point.

Usage, tips and tricks

Defining a Custom Element

class MyGreeting extends HTMLElement {
    static get observedAttributes() {
        return ['name'];
    }

    connectedCallback() {
        this.render();
    }

    attributeChangedCallback(name, oldValue, newValue) {
        this.render();
    }

    render() {
        const name = this.getAttribute('name') || 'World';
        this.textContent = `Hello, ${name}!`;
    }
}

customElements.define('my-greeting', MyGreeting);

Use in HTML:

<script type="module" src="my-greeting.js"></script>
<my-greeting name="Alice"></my-greeting>

Shadow DOM Encapsulation

class ShadowCard extends HTMLElement {
    connectedCallback() {
        const shadow = this.attachShadow({ mode: 'open' });
        shadow.innerHTML = `
            <style>
                p { color: steelblue; font-weight: bold; }
            </style>
            <p><slot></slot></p>
        `;
    }
}

customElements.define('shadow-card', ShadowCard);

Styles inside the Shadow DOM do not leak out, and styles from the main document do not bleed in.

HTML Template

<template id="card-template">
    <style>
        .card { border: 1px solid #ccc; padding: 1rem; }
    </style>
    <div class="card"><slot></slot></div>
</template>

<script>
class CardElement extends HTMLElement {
    connectedCallback() {
        const template = document.getElementById('card-template');
        const clone = template.content.cloneNode(true);
        this.attachShadow({ mode: 'open' }).appendChild(clone);
    }
}
customElements.define('card-element', CardElement);
</script>

With Lit

Lit is a minimal library that reduces boilerplate:

import { LitElement, html, css } from 'lit';

class MyCounter extends LitElement {
    static properties = { count: { type: Number } };
    static styles = css`button { font-size: 1.2rem; }`;

    constructor() {
        super();
        this.count = 0;
    }

    render() {
        return html`
            <button @click=${() => this.count++}>
                Clicked ${this.count} times
            </button>
        `;
    }
}

customElements.define('my-counter', MyCounter);

See also