Skip to content

Web Components Architecture

This lesson explains how Web Components fit into modern frontend architecture, how custom elements, Shadow DOM, templates, and events work together, and how to structure reusable component libraries for production.

What Is Web Components Architecture?

Web Components architecture describes how browser-native standards combine to create reusable, encapsulated UI building blocks. A well-designed component library separates public APIs, internal implementation, styling, and communication so teams can share UI safely across applications.

At a high level, architecture is built from three standards: Custom Elements for behavior, Shadow DOM for encapsulation, and HTML Templates for reusable markup.

Layer Role
Custom Elements Define new HTML tags and lifecycle behavior
Shadow DOM Encapsulate internal DOM and CSS
HTML Templates Store reusable markup safely
Slots Compose external content into components
Custom Events Send data outward to parent apps
Public API Attributes, properties, events, and styling hooks

Basic Component Architecture Example

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

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

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback() {
    this.render();
  }

  render() {
    const variant =
      this.getAttribute("variant") || "primary";
    const disabled =
      this.hasAttribute("disabled");

    this.shadowRoot.innerHTML = `
      <style>
        button {
          padding: 0.5rem 1rem;
          border-radius: 0.5rem;
        }
        .primary { background: #2563eb; color: white; }
        .secondary { background: #e5e7eb; color: #111827; }
      </style>
      <button class="${variant}" ?disabled="${disabled}">
        <slot></slot>
      </button>
    `;
  }
}

customElements.define("app-button", AppButton);

This example shows a common architecture pattern: lifecycle hooks manage rendering, Shadow DOM encapsulates styles, attributes define the public API, and slots allow flexible content.

Component Anatomy

Part Description Example
Host Element The custom element in the light DOM. <app-button>
Shadow Root Encapsulated internal tree. this.shadowRoot
Internal Template Private markup and styles. render() output
Slots Projection points for child content. <slot name="icon">
Public API What consumers can configure. Attributes and properties
Events Output channel to parent apps. button-click

Lifecycle Architecture

class DataPanel extends HTMLElement {
  connectedCallback() {
    console.log("Added to DOM");
    this.loadData();
  }

  disconnectedCallback() {
    console.log("Removed from DOM");
    this.cleanup();
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      this.updateFromAttribute(name, newValue);
    }
  }

  adoptedCallback() {
    console.log("Moved to another document");
  }
}

Lifecycle callbacks form the backbone of component architecture. They tell you when to render, fetch data, attach listeners, and release resources.

Callback When It Runs Typical Use
connectedCallback Element added to DOM Initial render, event listeners, data fetch
disconnectedCallback Element removed from DOM Cleanup timers, observers, listeners
attributeChangedCallback Observed attribute changes Sync UI with attribute updates
adoptedCallback Element moved to new document Reinitialize document-specific behavior

Communication Architecture

Web Components communicate through a predictable set of channels. Good architecture keeps data flow clear and avoids tight coupling between parent apps and internal implementation details.

// Input: attributes and properties
const card = document.querySelector("user-card");
card.setAttribute("role", "admin");
card.userId = 42;

// Output: custom events
card.addEventListener("profile-selected", (event) => {
  console.log(event.detail);
});

// Composition: slots
// <user-card>
//   <span slot="badge">Pro</span>
// </user-card>
Direction Mechanism Best For
Parent → Component Attributes and properties Configuration and state input
Component → Parent Custom events User actions and state changes
Parent → Component Slots Flexible markup and content
External → Internal Styles CSS custom properties and parts Theme and design tokens

Shadow DOM Architecture

class ModalDialog extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        :host { display: block; }
        .overlay {
          position: fixed;
          inset: 0;
          background: rgba(0, 0, 0, 0.45);
        }
        ::slotted(h2) {
          margin-top: 0;
        }
      </style>
      <div class="overlay" part="overlay">
        <div class="dialog" part="dialog">
          <slot name="title"></slot>
          <slot></slot>
        </div>
      </div>
    `;
  }
}

Shadow DOM creates a boundary between public page markup and private component internals. Use slots for composition, ::part() for limited styling hooks, and CSS custom properties for theming.

Composition Over Inheritance

<app-card>
  <h2 slot="title">Team Members</h2>
  <user-row name="Alex" role="Admin"></user-row>
  <user-row name="Sam" role="Editor"></user-row>
  <app-button slot="footer" variant="primary">
    Add Member
  </app-button>
</app-card>

Web Components architecture favors composition. Small focused elements such as <app-card>, <user-row>, and <app-button> combine into larger UI without deep inheritance trees.

Component Library Structure

ui-components/
├── src/
│   ├── button/
│   │   ├── app-button.js
│   │   └── app-button.test.js
│   ├── card/
│   │   ├── app-card.js
│   │   └── app-card.test.js
│   ├── tokens/
│   │   └── design-tokens.css
│   └── index.js
├── docs/
│   └── usage.md
└── package.json

Production architecture usually organizes one component per folder, shared design tokens in a central location, a single registration entry point, and documentation for attributes, events, and slots.

Framework Integration Architecture

// React wrapper pattern
function StatusBadge(props) {
  const ref = useRef(null);

  useEffect(() => {
    if (ref.current) {
      ref.current.status = props.status;
    }
  }, [props.status]);

  return (
    <status-badge ref={ref}>
      {props.children}
    </status-badge>
  );
}

Framework apps often consume Web Components directly, but wrappers can help synchronize properties, events, and SSR requirements when needed.

Common Architecture Patterns

Pattern Description Example
Presentational Components Focus on UI rendering and styling. <app-badge>
Container Components Fetch data and pass it to child elements. <user-list-panel>
Form Controls Expose value, validation, and events. <app-text-input>
Shell Components Provide layout and slot-based regions. <app-layout>
Design System Primitives Low-level reusable building blocks. <app-button>

Open vs Closed Shadow DOM

Mode Access When to Use
open element.shadowRoot is available Most components, testing, tooling, theming
closed Shadow root is hidden from outside code Strong encapsulation, third-party widgets

When This Architecture Helps Most

  • You are building a shared UI library for multiple teams.
  • You need consistent APIs across framework boundaries.
  • You want encapsulated styling without global CSS conflicts.
  • You are designing a design system with primitives and composites.
  • You need long-lived components that survive framework migrations.
  • You are planning micro frontends with independently deployable UI.

Architecture Use Cases

  • Company-wide design systems with buttons, inputs, and cards.
  • Embeddable widgets distributed to external websites.
  • Shared navigation and shell layouts across products.
  • Framework-agnostic form controls with validation APIs.
  • Micro frontend platforms with isolated UI packages.
  • Documentation sites that demo live custom elements.

Architecture Best Practices

  • Define a clear public API with attributes, properties, and events.
  • Keep components small and composable instead of monolithic.
  • Use Shadow DOM intentionally with a documented styling strategy.
  • Separate rendering, state sync, and event handling cleanly.
  • Document slots, CSS parts, and design tokens for consumers.
  • Clean up listeners and observers in disconnectedCallback.
  • Version and publish component libraries like any shared package.

Common Architecture Mistakes

  • Putting business logic, fetching, and UI in one giant component.
  • Exposing internal Shadow DOM details as the public API.
  • Using attributes for complex objects instead of properties.
  • Emitting too many unclear custom events.
  • Skipping cleanup when components are removed from the DOM.
  • Not planning theming with CSS custom properties.
  • Building components without tests for lifecycle and API behavior.

Key Takeaways

  • Web Components architecture combines Custom Elements, Shadow DOM, and Templates.
  • Lifecycle callbacks control render timing, setup, and cleanup.
  • Attributes, properties, slots, and events form the communication model.
  • Composition beats inheritance for scalable component libraries.
  • Good architecture makes shared UI reusable across apps and frameworks.

Pro Tip

Design each component around a single responsibility and a small public API. A stable architecture with clear inputs, outputs, and slots scales much better than one large custom element that tries to do everything.