Compound Components
Compound Components
The Compound Components pattern is an advanced technique used to build expressive, flexible, and highly readable React components.
It is used when multiple components must work together to achieve a single task, and they need to share implicit state between them without forcing the user to manually pass props around.
Classic examples from standard HTML include <select> and <option>, or <table>, <tr>, and <td>.
The Problem
Imagine building an Accordion component. A basic implementation might look like this:
// ❌ The Rigid Approach
<Accordion
items={[
{ title: 'Item 1', content: 'Content 1' },
{ title: 'Item 2', content: 'Content 2' }
]}
/>
This is rigid. What if the user wants an icon next to ‘Item 1’ but not ‘Item 2’? What if they want custom styling on specific content blocks? You would have to add an infinite number of configuration props (renderIcon, contentClassName, etc.), making the component bloated and hard to use.
The Compound Component Approach
Instead of a single massive configuration object, you provide several smaller components that are designed to be composed together.
// ✅ The Compound Approach
<Accordion>
<Accordion.Item>
<Accordion.Header>Item 1 <Icon /></Accordion.Header>
<Accordion.Content>Content 1</Accordion.Content>
</Accordion.Item>
<Accordion.Item>
<Accordion.Header>Item 2</Accordion.Header>
<Accordion.Content>Content 2</Accordion.Content>
</Accordion.Item>
</Accordion>
This is infinitely flexible. The user controls the exact JSX tree. But how do the header and content know whether they are expanded or collapsed without the user managing isOpen state manually?
How it Works (Using Context)
Compound components rely on React Context to share state implicitly between the parent and its deeply nested children.
import { createContext, useContext, useState } from 'react';
// 1. Create a Context
const AccordionContext = createContext();
// 2. Create the Parent Component
function Accordion({ children }) {
const [openIndex, setOpenIndex] = useState(null);
return (
<AccordionContext.Provider value={{ openIndex, setOpenIndex }}>
<div className="accordion">{children}</div>
</AccordionContext.Provider>
);
}
// 3. Create the Child Components
// They use the Context to read/write state without the user knowing
function Item({ index, children }) {
return <div className="accordion-item">{/* ... */}</div>;
}
function Header({ index, children }) {
const { openIndex, setOpenIndex } = useContext(AccordionContext);
const isOpen = openIndex === index;
return (
<button onClick={() => setOpenIndex(isOpen ? null : index)}>
{children} {isOpen ? '[-]' : '[+]'}
</button>
);
}
function Content({ index, children }) {
const { openIndex } = useContext(AccordionContext);
return openIndex === index ? <div>{children}</div> : null;
}
// 4. (Optional) Attach children to the parent for easier imports
Accordion.Item = Item;
Accordion.Header = Header;
Accordion.Content = Content;
export default Accordion;
Benefits
- API Elegance: The API is declarative and readable.
- Inversion of Control: The user decides the structure and styling (putting an Icon inside the Header), while your library handles the complex internal state logic.
- No Prop Drilling: Users don’t have to manually pass
isOpenortoggleOpento every child.
Interview Questions
Q: What is the Compound Components pattern?
A: It is a pattern where multiple distinct components work together under the hood to share implicit state (usually via Context) while giving the developer flexible, declarative markup. For example, rendering <Tabs>, <TabList>, and <TabPanel> where <Tabs> manages the active tab state and implicitly shares it with the children, avoiding the need for messy prop-drilling.