Compound Components
HardCompound components are related components that share implicit state through Context, forming a cohesive API. The parent manages state and children consume it, giving users full control over rendering order and composition while hiding internal wiring. Libraries like Radix UI use this pattern extensively.
Interactive Visualization
Key Points
- Parent component manages shared state via Context Provider
- Child components consume the shared context to coordinate behavior
- Consumers control rendering order and composition freely
- Internal state sharing is hidden from the public API
- Validation can enforce that children are used within the correct parent
- Named sub-components (e.g., Tabs.Panel) provide a clear API
Code Examples
Tabs Compound Component
import { createContext, useContext, useState, type ReactNode } from 'react' interface TabsContextValue { activeTab: string setActiveTab: (id: string) => void } const TabsContext = createContext<TabsContextValue | null>(null) function useTabsContext(): TabsContextValue { const ctx = useContext(TabsContext) if (!ctx) throw new Error('Tab components must be used within Tabs') return ctx } function Tabs({ children, defaultTab }: { children: ReactNode; defaultTab: string }) { const [activeTab, setActiveTab] = useState(defaultTab) return ( <TabsContext.Provider value={{ activeTab, setActiveTab }}> {children} </TabsContext.Provider> ) } function TabList({ children }: { children: ReactNode }) { return <div role="tablist">{children}</div> } function Tab({ id, children }: { id: string; children: ReactNode }) { const { activeTab, setActiveTab } = useTabsContext() return ( <button role="tab" aria-selected={activeTab === id} onClick={() => setActiveTab(id)} > {children} </button> ) } function TabPanel({ id, children }: { id: string; children: ReactNode }) { const { activeTab } = useTabsContext() return activeTab === id ? <div role="tabpanel">{children}</div> : null } // Usage function App() { return ( <Tabs defaultTab="overview"> <TabList> <Tab id="overview">Overview</Tab> <Tab id="details">Details</Tab> </TabList> <TabPanel id="overview">Overview content</TabPanel> <TabPanel id="details">Details content</TabPanel> </Tabs> ) }
Each sub-component consumes the shared TabsContext, allowing consumers to freely compose and reorder Tab and TabPanel elements
Common Mistakes
- Using compound components for simple cases where props would be cleaner
- Not providing a helpful error message when child components are used outside the parent
- Exposing too much internal state through the shared context
Interview Tips
- Compare compound components to configuration-based APIs and explain the flexibility trade-off
- Reference real-world libraries like Radix UI or Headless UI that use this pattern
- Discuss how context keeps the internal wiring hidden from consumers