What does the "compound components" pattern do with context?
What "Compound Components" are
Compound Components (composite components) are a pattern where several related components work together as a single "component group".
Usually we have a "parent" and "child elements" that communicate with each other through React Context instead of passing props manually.
Example (intuitively)
Without the pattern
javascript
<Tabs>
<TabList>
<Tab label="Description" activeTab={activeTab} setActiveTab={setActiveTab} />
<Tab label="Reviews" activeTab={activeTab} setActiveTab={setActiveTab} />
</TabList>
<TabPanels activeTab={activeTab}>
<TabPanel index={0}>Content 1</TabPanel>
<TabPanel index={1}>Content 2</TabPanel>
</TabPanels>
</Tabs>Downsides:
- you have to thread a bunch of props (
activeTab,setActiveTab); - the components are "tightly coupled" - using them separately is inconvenient.
With the Compound Components pattern
Goal:
- For
Tabs,TabList,Tab,TabPanelto know their own state on their own through an internal context; - for the code to look natural:
javascript
<Tabs>
<Tabs.List>
<Tabs.Tab>Description</Tabs.Tab>
<Tabs.Tab>Reviews</Tabs.Tab>
</Tabs.List>
<Tabs.Panels>
<Tabs.Panel>Content 1</Tabs.Panel>
<Tabs.Panel>Content 2</Tabs.Panel>
</Tabs.Panels>
</Tabs>How it works
1. Create the context
javascript
const TabsContext = createContext(null);2. The parent component (Provider)
javascript
function Tabs({ children }) {
const [activeIndex, setActiveIndex] = useState(0);
const value = useMemo(() => ({ activeIndex, setActiveIndex }), [activeIndex]);
return (
<TabsContext.Provider value={value}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}3. Child components - context subscribers
javascript
function TabList({ children }) {
return <div className="tab-list">{children}</div>;
}
function Tab({ children, index }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
const isActive = index === activeIndex;
return (
<button
className={isActive ? "active" : ""}
onClick={() => setActiveIndex(index)}
>
{children}
</button>
);
}
function Panels({ children }) {
return <div className="tab-panels">{children}</div>;
}
function Panel({ children, index }) {
const { activeIndex } = useContext(TabsContext);
return activeIndex === index ? <div>{children}</div> : null;
}4. Assembling the "compound" component
javascript
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panels = Panels;
Tabs.Panel = Panel;What context does here
Context is used as an internal linking mechanism between components, so they can "know" the parent's (Tabs) state without requiring props at every level.
That is:
Tabsmanages the state (the active tab);Tabs.TabandTabs.Panelare simply "subscribed" to that context;- everything synchronizes automatically.
Advantages of the pattern
| Advantage | Description |
|---|---|
| Clean API | Nested JSX reflects the logic's hierarchy |
| Reusability | Components can be reused independently |
| Encapsulation | The Tabs state is hidden, accessible only through context |
| Flexibility | You can insert your own components between them |
| No prop drilling | No props are passed down manually |
Where this is used
This pattern is used everywhere:
- in Headless UI, Radix UI, shadcn/ui, React Aria;
- for Tabs, Accordion, Dropdown, Menu, Modal, Form, Stepper.
Summary
Compound Components + Context =
a flexible interface where child components automatically know which context they are in, and synchronize without props.
The pattern's formula:
- Create a context inside the "parent";
- Provide the value through
<Provider>; - Child components pull the state from the context;
- Export the child components as properties of the main one.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.