Back to Curriculum

Web Components and Custom Elements

📚 Lesson 15 of 20 ⏱️ 50 min

Web Components and Custom Elements

50 min

Web Components are a set of web platform APIs that enable you to create reusable, encapsulated custom HTML elements. They consist of four main technologies: Custom Elements (define new HTML tags), Shadow DOM (encapsulated styling and markup), HTML Templates (`<template>` and `<slot>` elements), and ES Modules (for importing/exporting). Web Components work across modern browsers and frameworks, providing a standards-based approach to component development.

Custom Elements allow you to define your own HTML elements with custom behavior, properties, and methods. You create custom elements by extending the `HTMLElement` class and registering them with `customElements.define()`. Custom elements have lifecycle callbacks: `connectedCallback()` (when added to DOM), `disconnectedCallback()` (when removed), `adoptedCallback()` (when moved to new document), and `attributeChangedCallback()` (when attributes change). This enables rich, interactive components that behave like native HTML elements.

Shadow DOM provides style and markup encapsulation, preventing styles from leaking out and external styles from affecting component internals. You attach a shadow root using `element.attachShadow({mode: 'open'})` or `'closed'` (closed shadow DOM isn't accessible via JavaScript). Styles within shadow DOM are scoped to that component, and you can use `<slot>` elements to project light DOM content into shadow DOM, enabling flexible component composition.

HTML Templates (`<template>`) define reusable markup that isn't rendered until cloned and inserted into the DOM. Templates are perfect for Web Components—you can define component structure in a template, clone it in the constructor, and insert it into shadow DOM. The `<slot>` element within templates creates insertion points for content projection, allowing parent elements to inject content into component slots. This enables flexible, composable components.

Web Components promote reusability, maintainability, and framework independence. They work with any framework (React, Vue, Angular) or vanilla JavaScript, making them ideal for design systems and shared component libraries. However, Web Components have some limitations: they don't have built-in data binding (you handle it manually), browser support varies (though good in modern browsers), and they require more boilerplate than framework components. They're best for reusable UI components that need to work across different projects or frameworks.

Best practices include using semantic custom element names (must contain a hyphen, like `my-component`), providing proper accessibility attributes, handling edge cases in lifecycle callbacks, using closed shadow DOM only when necessary (open is usually better for debugging), and documenting component APIs clearly. Web Components enable you to build truly reusable, framework-agnostic components that work everywhere, making them valuable for large-scale applications and design systems.

Key Concepts

  • Web Components consist of Custom Elements, Shadow DOM, Templates, and ES Modules.
  • Custom Elements let you define new HTML tags with custom behavior.
  • Shadow DOM provides style and markup encapsulation.
  • HTML Templates define reusable markup that isn't rendered until cloned.
  • Web Components work across frameworks and promote reusability.

Learning Objectives

Master

  • Creating custom HTML elements with Custom Elements API
  • Using Shadow DOM for style and markup encapsulation
  • Working with HTML Templates and slots for component composition
  • Understanding Web Components lifecycle and best practices

Develop

  • Component-based architecture thinking
  • Understanding encapsulation and reusability
  • Creating framework-agnostic, reusable components

Tips

  • Custom element names must contain a hyphen (e.g., my-component).
  • Use Shadow DOM for style encapsulation and component isolation.
  • Leverage HTML Templates and slots for flexible component composition.
  • Handle lifecycle callbacks properly for component initialization and cleanup.

Common Pitfalls

  • Forgetting the hyphen in custom element names (required by spec).
  • Not handling lifecycle callbacks, causing memory leaks or broken functionality.
  • Overusing closed shadow DOM, making debugging difficult.
  • Not providing proper accessibility attributes for custom elements.

Summary

  • Web Components enable reusable, encapsulated custom HTML elements.
  • Custom Elements define new HTML tags with custom behavior.
  • Shadow DOM provides style and markup encapsulation.
  • HTML Templates and slots enable flexible component composition.
  • Web Components work across frameworks and promote reusability.

Exercise

Create a custom HTML element with encapsulated styles and behavior.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Custom Elements</title>
</head>
<body>
  <h1>Custom Element Example</h1>
  
  <!-- Custom element usage -->
  <user-card name="John Doe" email="john@example.com"></user-card>
  
  <script>
    // Define custom element
    class UserCard extends HTMLElement {
      constructor() {
        super();
        
        // Create shadow DOM
        const shadow = this.attachShadow({mode: 'open'});
        
        // Get attributes
        const name = this.getAttribute('name');
        const email = this.getAttribute('email');
        
        // Create component HTML
        shadow.innerHTML = `
          <style>
            .card {
              border: 1px solid #ccc;
              border-radius: 8px;
              padding: 16px;
              margin: 16px;
              box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            }
            .name {
              font-size: 18px;
              font-weight: bold;
              color: #333;
            }
            .email {
              color: #666;
              margin-top: 8px;
            }
          </style>
          <div class="card">
            <div class="name">${name}</div>
            <div class="email">${email}</div>
          </div>
        `;
      }
    }
    
    // Register custom element
    customElements.define('user-card', UserCard);
  </script>
</body>
</html>

Exercise Tips

  • Add lifecycle callbacks (connectedCallback, disconnectedCallback) to your custom element.
  • Use attributeChangedCallback to react to attribute changes.
  • Experiment with slots to allow content projection into your component.
  • Try using HTML Templates instead of template literals for component structure.

Code Editor

Output