Skip to content

Web Components Basics

This lesson explains Web Components Basics with clear examples, use cases, and best practices so you can build reusable Web Components confidently.

Web Components Basics Overview

Web Components are a set of browser-native technologies that allow developers to create reusable, encapsulated, framework-friendly UI components using standard HTML, CSS, and JavaScript.

A Web Component can define its own custom HTML tag, render internal UI, isolate styles with Shadow DOM, expose slots for content, react to attributes, dispatch custom events, and work in plain JavaScript, React, Angular, Vue, Svelte, Astro, and server-rendered pages.

Web Components Basics List

  • 1. Web Components are browser-native components.
    They do not require a frontend framework. They are built using standard browser APIs.
    <my-card>
      Hello Web Components
    </my-card>
  • 2. Web Components use custom elements.
    A custom element is a reusable HTML tag created with JavaScript. Custom element names must include a hyphen.
    <user-profile></user-profile>
    <app-header></app-header>
    <product-card></product-card>
  • 3. Define a custom element with a class.
    Custom elements extend HTMLElement.
    class MyGreeting extends HTMLElement {
      connectedCallback() {
        this.textContent =
          "Hello from a Web Component!";
      }
    }
    
    customElements.define(
      "my-greeting",
      MyGreeting
    );
  • 4. Register custom elements with customElements.define().
    The browser connects a custom tag name to a JavaScript class.
    customElements.define(
      "my-button",
      MyButton
    );
  • 5. Custom element names must contain a hyphen.
    This prevents conflicts with built-in HTML tags.
    <my-button></my-button>
    
    <button-card></button-card>
  • 6. Use connectedCallback() for setup.
    connectedCallback() runs when the element is added to the DOM. It is commonly used for rendering, event listeners, and setup.
    connectedCallback() {
      this.innerHTML =
        `
          <p>
            Component is connected.
          </p>
        `;
    }
  • 7. Use disconnectedCallback() for cleanup.
    disconnectedCallback() runs when the element is removed from the DOM. Use it to clean up timers, observers, and global listeners.
    connectedCallback() {
      this.timer =
        setInterval(() => {
          console.log("Running");
        }, 1000);
    }
    
    disconnectedCallback() {
      clearInterval(this.timer);
    }
  • 8. Use Shadow DOM for encapsulation.
    Shadow DOM keeps component markup and styles separate from the main page.
    class MyCard extends HTMLElement {
      constructor() {
        super();
    
        this.attachShadow(
          { mode: "open" }
        );
      }
    
      connectedCallback() {
        this.shadowRoot.innerHTML =
          `
            <style>
              article {
                border: 1px solid #dbe5f1;
                padding: 1rem;
              }
            </style>
    
            <article>
              <slot></slot>
            </article>
          `;
      }
    }
  • 9. Use slots for flexible content.
    Slots allow users to pass content into a Web Component.
    <my-card>
      <h2>Profile</h2>
      <p>User information goes here.</p>
    </my-card>
    <article>
      <slot></slot>
    </article>
  • 10. Use named slots for specific areas.
    Named slots help organize content such as title, body, footer, and actions.
    <my-panel>
      <h2 slot="title">
        Settings
      </h2>
    
      <p>
        Update your preferences.
      </p>
    
      <button slot="actions">
        Save
      </button>
    </my-panel>
    <header>
      <slot name="title"></slot>
    </header>
    
    <section>
      <slot></slot>
    </section>
    
    <footer>
      <slot name="actions"></slot>
    </footer>
  • 11. Use attributes for simple configuration.
    Attributes allow users to configure Web Components directly in HTML.
    <my-alert
      type="success"
      message="Saved successfully"
      open>
    </my-alert>
  • 12. Read attributes inside a component.
    Use getAttribute() to read string values from the host element.
    get type() {
      return this.getAttribute("type") || "info";
    }
    
    get message() {
      return this.getAttribute("message") || "";
    }
  • 13. Use properties for complex data.
    Properties are better for arrays, objects, callbacks, maps, services, and non-string values.
    const table =
      document.querySelector("my-table");
    
    table.rows =
      [
        { id: 1, name: "Alice" },
        { id: 2, name: "Bob" }
      ];
  • 14. Observe attributes that affect rendering.
    Use observedAttributes to tell the browser which attributes should trigger updates.
    static get observedAttributes() {
      return [
        "type",
        "message",
        "open"
      ];
    }
  • 15. React to changes with attributeChangedCallback().
    This lifecycle method runs when an observed attribute changes.
    attributeChangedCallback(name, oldValue, newValue) {
      if (oldValue === newValue) {
        return;
      }
    
      this.render();
    }
  • 16. Dispatch custom events.
    Web Components communicate user actions through custom events.
    this.dispatchEvent(
      new CustomEvent(
        "save-click",
        {
          detail: {
            id: 101
          },
          bubbles: true,
          composed: true
        }
      )
    );
  • 17. Listen to custom events outside the component.
    Parent pages or frameworks can listen for component events.
    document
      .querySelector("my-editor")
      .addEventListener("save-click", (event) => {
        console.log(event.detail.id);
      });
  • 18. Use bubbles and composed for Shadow DOM events.
    bubbles lets the event travel up the DOM. composed allows the event to cross the Shadow DOM boundary.
    new CustomEvent(
      "item-select",
      {
        detail: {
          value: "dashboard"
        },
        bubbles: true,
        composed: true
      }
    );
  • 19. Style the host with :host.
    :host targets the custom element itself from inside Shadow DOM styles.
    :host {
      display: block;
    }
    
    :host([hidden]) {
      display: none;
    }
  • 20. Style slotted content with ::slotted().
    ::slotted() styles light DOM content assigned to a slot, but only targets the slotted element itself.
    ::slotted(h2) {
      margin-top: 0;
    }
  • 21. Expose styling hooks with CSS parts.
    Use part inside a component and ::part() from outside to expose safe styling hooks.
    <my-card></my-card>
    
    <style>
      my-card::part(title) {
        font-weight: 700;
      }
    </style>
  • 22. Use CSS custom properties for theming.
    CSS variables can pass theme values from the page into Shadow DOM.
    :host {
      color: var(--card-text, #111827);
      background: var(--card-bg, #ffffff);
    }
  • 23. Use adopted stylesheets for reusable styles.
    Constructable stylesheets can be shared across multiple Web Components.
    const sheet =
      new CSSStyleSheet();
    
    sheet.replaceSync(
      `
        :host {
          display: block;
        }
      `
    );
    
    this.shadowRoot.adoptedStyleSheets =
      [sheet];
  • 24. Use templates for reusable markup.
    The <template> element stores reusable HTML that can be cloned into components.
    <template id="card-template">
      <style>
        article {
          border: 1px solid #dbe5f1;
          padding: 1rem;
        }
      </style>
    
      <article>
        <slot></slot>
      </article>
    </template>
  • 25. Clone templates into Shadow DOM.
    Template cloning avoids rebuilding large markup strings repeatedly.
    const template =
      document.querySelector("#card-template");
    
    this.shadowRoot.appendChild(
      template.content.cloneNode(true)
    );
  • 26. Make components accessible.
    Use semantic HTML, labels, ARIA states, keyboard support, focus styles, and predictable behavior.
    <button
      type="button"
      aria-expanded="false">
      Menu
    </button>
  • 27. Prefer native elements inside components.
    Native elements already provide accessibility and keyboard behavior.
    <button type="button">
      Save
    </button>
    
    <input type="email" />
  • 28. Clean up event listeners and observers.
    Global listeners, timers, observers, and subscriptions should be cleaned up in disconnectedCallback().
    connectedCallback() {
      window.addEventListener(
        "resize",
        this.handleResize
      );
    }
    
    disconnectedCallback() {
      window.removeEventListener(
        "resize",
        this.handleResize
      );
    }
  • 29. Web Components work with frameworks.
    Since Web Components are browser-native elements, they can be used in many frameworks and static sites.
    <product-card
      product-id="101"
      title="Laptop | PHPKINGDOM">
    </product-card>
  • 30. Web Components are ideal for design systems.
    They are useful for reusable UI libraries, cross-framework components, widgets, dashboards, documentation sites, and enterprise design systems.

Complete Basic Web Component Example

This example shows a reusable card component with Shadow DOM, slots, attributes, styling, and a custom event.

class MyCard extends HTMLElement {
  static get observedAttributes() {
    return [
      "variant"
    ];
  }

  constructor() {
    super();

    this.attachShadow(
      { mode: "open" }
    );

    this.shadowRoot.innerHTML =
      `
        <style>
          :host {
            display: block;
            font-family: system-ui, sans-serif;
          }

          article {
            border: 1px solid #dbe5f1;
            border-radius: 0.75rem;
            padding: 1rem;
            background: var(--card-bg, #ffffff);
          }

          :host([variant="highlight"]) article {
            border-width: 2px;
          }

          ::slotted([slot="title"]) {
            margin-top: 0;
          }
        </style>

        <article part="card">
          <header>
            <slot name="title"></slot>
          </header>

          <section>
            <slot></slot>
          </section>

          <footer>
            <button type="button">
              Select
            </button>
          </footer>
        </article>
      `;
  }

  connectedCallback() {
    this.shadowRoot
      .querySelector("button")
      .addEventListener("click", () => {
        this.dispatchEvent(
          new CustomEvent(
            "card-select",
            {
              detail: {
                variant: this.variant
              },
              bubbles: true,
              composed: true
            }
          )
        );
      });
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue === newValue) {
      return;
    }

    console.log(
      name + " changed to " + newValue
    );
  }

  get variant() {
    return this.getAttribute("variant") || "default";
  }

  set variant(value) {
    this.setAttribute(
      "variant",
      value
    );
  }
}

customElements.define(
  "my-card",
  MyCard
);
<my-card variant="highlight">
  <h2 slot="title">
    Web Components
  </h2>

  <p>
    Build reusable browser-native UI.
  </p>
</my-card>
document
  .querySelector("my-card")
  .addEventListener("card-select", (event) => {
    console.log(event.detail.variant);
  });

Core Web Components APIs Table

API Purpose Example
Custom Elements Create custom HTML tags. customElements.define()
Shadow DOM Encapsulate markup and styles. attachShadow()
Slots Pass content into components. <slot>
Templates Store reusable markup. <template>
Attributes Configure components from HTML. variant="primary"
Custom Events Communicate actions to parent code. new CustomEvent()

Common Web Components Use Cases

  • Cross-framework design systems.
  • Reusable UI widgets.
  • Enterprise component libraries.
  • Documentation website components.
  • Static site and CMS widgets.
  • Dashboards and admin panels.
  • Micro frontend shared UI.
  • Framework-independent components.
  • Embeddable third-party widgets.
  • Long-lived reusable UI elements.

Common Web Components Basics Mistakes

  • Forgetting the hyphen in custom element names.
  • Rendering repeatedly in connectedCallback() without guards.
  • Not cleaning up global event listeners.
  • Using attributes for complex objects instead of properties.
  • Not using composed: true for events crossing Shadow DOM.
  • Forgetting accessibility and keyboard support.
  • Overusing Shadow DOM when simple light DOM is enough.
  • Expecting page CSS to automatically style Shadow DOM internals.
  • Not documenting attributes, properties, events, and slots.
  • Creating components that look native but do not behave accessibly.

Web Components Basics Best Practices

  • Use clear, kebab-case custom element names.
  • Keep components focused on one responsibility.
  • Use Shadow DOM when style or DOM encapsulation is needed.
  • Use slots for flexible content composition.
  • Use attributes for simple configuration.
  • Use properties for complex data.
  • Dispatch custom events for user actions.
  • Clean up timers, observers, and listeners.
  • Build accessibility into every component from the start.
  • Document public APIs: attributes, properties, slots, parts, and events.

Pro Tip

Web Components are strongest when you design them like public APIs: simple attributes, powerful properties, meaningful slots, stable events, accessible behavior, and clear documentation.