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.