Lab 03: Web Components Architecture

Time: 60 minutes | Level: Architect | Docker: node:20-alpine

Overview

Build production-grade Web Components using vanilla Custom Elements v1 API: Shadow DOM, slots, ::part(), CSS custom properties crossing the shadow boundary, element registry collision detection, and component composition patterns — no framework dependencies.


Step 1: Custom Element Lifecycle

class DsButton extends HTMLElement {
  // Observed attributes trigger attributeChangedCallback
  static get observedAttributes() {
    return ['variant', 'size', 'disabled', 'loading'];
  }

  constructor() {
    super();
    // Always call super() first
    this._shadow = this.attachShadow({ mode: 'open' });
    this._state = { loading: false };
  }

  // Lifecycle: element added to DOM
  connectedCallback() {
    this._render();
    this._setupEventListeners();
  }

  // Lifecycle: element removed from DOM
  disconnectedCallback() {
    this._cleanup();
  }

  // Lifecycle: element moved to new document
  adoptedCallback() {
    console.log('Element adopted into new document');
  }

  // Lifecycle: observed attribute changed
  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue === newValue) return;
    if (name === 'loading') this._state.loading = newValue !== null;
    this._render();
  }
}

Step 2: Shadow DOM — Slots and Parts

💡 CSS custom properties do cross the shadow boundary (they inherit). ::part() provides a controlled styling API. Never pierce the shadow DOM with /deep/ or >>> — they're deprecated.


Step 3: Element Registry with Collision Detection


Step 4: Component Composition Patterns


Step 5: CSS Custom Properties API Surface

💡 Registered properties (via CSS.registerProperty) enable CSS transitions on custom properties — critical for animation systems.


Step 6: Reactive State Pattern


Step 7: Event Communication Pattern


Step 8: Capstone — Lifecycle Verification

Verified by running the lifecycle script (see test.js mounted via volume):

📸 Verified Output:


Summary

Concept
API
Notes

Element definition

customElements.define()

Hyphenated name required

Lifecycle hooks

connected/disconnected/adoptedCallback

Always call super()

Shadow DOM

attachShadow({ mode: 'open' })

Use open for tooling

Named slots

<slot name="header">

Content projection API

Part exposure

part="base", ::part(base)

Controlled style API

CSS inheritance

--custom-props

Cross shadow boundary

Registered properties

CSS.registerProperty()

Enable transitions

Collision detection

Custom registry map

Multi-version safety

Custom events

composed: true

Bubble through shadow DOM

Last updated