Isaac.

Web Components Basics

Create reusable custom HTML elements with Web Components.

By EMEPublished: February 20, 2025
web componentscustom elementsshadow domslots

A Simple Analogy

Web Components are like LEGO bricks. Self-contained, reusable pieces that work together without conflict.


Why Web Components?

  • Reusable: Use across projects
  • Encapsulation: Isolated styles/scripts
  • Framework-agnostic: Work with any framework
  • Standard: Native browser support
  • Composable: Build complex UIs

Custom Element

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
  }
  
  connectedCallback() {
    this.render();
    this.querySelector('button').addEventListener('click', () => {
      this.count++;
      this.render();
    });
  }
  
  render() {
    this.innerHTML = `
      <p>Count: ${this.count}</p>
      <button>Increment</button>
    `;
  }
}

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

Shadow DOM

class MyCard extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>
        .card { border: 1px solid #ccc; padding: 16px; }
        h2 { margin: 0; color: #333; }
      </style>
      <div class="card">
        <h2>My Card</h2>
        <slot></slot>
      </div>
    `;
  }
}

customElements.define('my-card', MyCard);
<my-card>
  <p>This content goes in the slot</p>
</my-card>

Slots (Content Projection)

class MyLayout extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>
        .container { display: flex; gap: 16px; }
        .header { background: #f0f0f0; padding: 16px; }
        .content { flex: 1; }
      </style>
      <div class="container">
        <div class="header">
          <slot name="header"></slot>
        </div>
        <div class="content">
          <slot></slot>
        </div>
      </div>
    `;
  }
}

customElements.define('my-layout', MyLayout);
<my-layout>
  <h1 slot="header">Welcome</h1>
  <p>Main content here</p>
</my-layout>

Attributes & Properties

class MyButton extends HTMLElement {
  get disabled() {
    return this.hasAttribute('disabled');
  }
  
  set disabled(value) {
    if (value) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }
  
  connectedCallback() {
    this.update();
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'disabled') {
      this.update();
    }
  }
  
  static get observedAttributes() {
    return ['disabled'];
  }
  
  update() {
    this.style.opacity = this.disabled ? '0.5' : '1';
  }
}

customElements.define('my-button', MyButton);

Best Practices

  1. Semantic: Use meaningful element names (dash required)
  2. Encapsulation: Use Shadow DOM for style isolation
  3. Documentation: Document attributes and slots
  4. Accessibility: Include ARIA labels
  5. Performance: Lazy load templates

Related Concepts

  • Custom Elements v1
  • Template element
  • Lit library (helpers)
  • Framework integration

Summary

Create reusable custom HTML elements with Shadow DOM for encapsulation and slots for flexible content projection.