Frontend Patterns

Pattern

Compound Component

Create components that work together implicitly through shared context to manage complex interactions.

Component Beginner

Compound Component

Problem

Complex UI components like tabs, accordions, or select menus require passing dozens of properties through multiple levels to coordinate behavior. Parent components become cluttered with implementation details, and consumers struggle to understand which properties go where, leading to brittle, hard-to-maintain component APIs.

Solution

Design parent components that manage shared state while child components handle specific UI concerns, communicating through context rather than props. This creates flexible APIs where consumers compose the pieces they need without prop drilling.

Example

This example demonstrates a Tabs compound component where the parent manages which tab is active while child components handle their own rendering.

// Parent component manages shared state and coordinates children
class Tabs extends HTMLElement {
  constructor() {
    super();
    // Track which tab is currently active
    this.activeIndex = 0;
  }

  connectedCallback() {
    // Listen for tab click events from child tab components
    this.addEventListener('tab-click', (e) => {
      // Update active tab based on which tab was clicked
      this.activeIndex = e.detail.index;
      // Sync the panels to show only the active one
      this.updatePanels();
    });
  }

  updatePanels() {
    // Find all tab panel children
    const panels = this.querySelectorAll('tab-panel');
    // Show only the panel that matches the active tab index
    panels.forEach((panel, i) => {
      panel.hidden = i !== this.activeIndex;
    });
  }
}

// Usage in HTML:
// <tabs-component>
//   <tab-list>
//     <tab-item>Profile</tab-item>
//     <tab-item>Settings</tab-item>
//   </tab-list>
//   <tab-panel>Profile content</tab-panel>
//   <tab-panel>Settings content</tab-panel>
// </tabs-component>

Benefits

  • Creates intuitive, declarative APIs that read like natural markup.
  • Eliminates prop drilling by using context for internal communication.
  • Gives consumers flexibility to arrange and style child components.
  • Reduces the number of props needed in the public API.
  • Makes complex component interactions feel simple and composable.

Tradeoffs

  • Requires understanding of context and implicit communication patterns.
  • Child components are tightly coupled to parent context.
  • Can be confusing when components are used outside their intended parent.
  • Harder to enforce required children or validate composition structure.
  • May hide important relationships that would be explicit with props.
Stay Updated

Get New Patterns
in Your Inbox

Join thousands of developers receiving weekly insights on frontend architecture patterns

No spam. Unsubscribe anytime.