Insights & examples inspired by *“Learning Patterns” by Lydia Hallie & Addy Osmani***
“When multiple elements belong together and need to share implicit state, treat them as compound components so consumers compose, not configure.”
1 What is the Compound Component Pattern?
It’s a design pattern where several sub‑components collaborate under a single parent that holds the state and logic. Each child is responsible only for rendering its slice of UI, but they communicate through the parent’s React Context (or cloneElement). This yields APIs that feel like native HTML combos such as <select> + <option> or <details> + <summary>.
<Toggle>
<Toggle.On>Light is ON</Toggle.On>
<Toggle.Off>Light is OFF</Toggle.Off>
<Toggle.Button />
</Toggle>
The consumer decides ordering and layout; the parent Toggle silently wires up state.
2 Why use it?
| Benefit | How it helps |
|---|---|
| Implicit wiring | Consumers don’t pass callbacks or state props—less prop‑drilling, fewer mistakes. |
| Flexible composition | UI authors reorder, omit, or duplicate parts as needed. |
| Reusability | Sub‑components are tiny & focused; you can reuse them elsewhere. |
| Accessibility wins | The parent guarantees correct ARIA roles/labels, even if children shuffle. |
Patterns.dev emphasises that compound components shine when parts depend on each other yet need independent markup freedom.
3 Implementing a simple Toggle
3.1 Create shared context
const ToggleContext = React.createContext();
3.2 Parent with state
function Toggle({ children }) {
const [on, setOn] = React.useState(false);
const toggle = () => setOn(!on);
const value = React.useMemo(() => ({ on, toggle }), [on]);
return (
<ToggleContext.Provider value={value}>
{children}
</ToggleContext.Provider>
);
}
3.3 Children consume context
Toggle.On = function On({ children }) {
const { on } = React.useContext(ToggleContext);
return on ? children : null;
};
Toggle.Off = function Off({ children }) {
const { on } = React.useContext(ToggleContext);
return on ? null : children;
};
Toggle.Button = function Button(props) {
const { on, toggle } = React.useContext(ToggleContext);
return (
<button onClick={toggle} aria-pressed={on} {...props}>
{on ? "Switch off" : "Switch on"}
</button>
);
};
Using React Context keeps the API “clean and intuitive,” mirroring native controls.
4 Five Real‑Life Software Use Cases
-
Radix UI –
<Tabs.List>,<Tabs.Trigger>,<Tabs.Content>all inherit the parentTabscontext, letting developers rearrange tab parts freely. -
React Router –
<Routes>manages location state; nested<Route>elements render depending on it, no manual prop passing. -
Headless UI / Menu –
<Menu>,<Menu.Button>,<Menu.Items>,<Menu.Item>cooperate for accessible dropdowns. -
Material‑UI Form –
<FormGroup>provides context to<FormControlLabel>and<Checkbox>to sync value and validation. -
Downshift Toggle / Select – Kent C. Dodds’ library exposes compound children like
<Downshift.Item>to build custom autocompletes with minimal glue.
5 When not to use it
If children don’t depend on shared state, or if you need strict structure (e.g., fixed HTML), simpler patterns like Container/Presentational may suffice. Overusing compound components can hide data‑flow and complicate debugging. Balance is key.
6 Key take‑aways
- Design for composition, not configuration.
- Parent holds logic + accessibility; children hold markup.
- Ideal for dropdowns, tabs, menus, forms, toggles—any clustered UI with inter‑dependent pieces.
Content sources: patterns.dev (Learning Patterns) and related community articles (citations above). Licensed under CC BY‑NC 4.0 where applicable.
Top comments (0)