Compound Components Pattern Explained0%

Compound Components Pattern Explained

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Compound Components Pattern Explained

1. What Are Compound Components?

In native HTML, certain elements work together as a synchronized family to provide an intuitive, declarative interface. The classic example is <select> and <option>:

HTML
<select name="course">
<option value="react">React Mastery</option>
<option value="django">Django Architecture</option>
</select>

Notice how:

  • The <select> manages the selected state and keyboard navigation.
  • The <option> tags configure individual items.
  • You don't have to pass an awkward configuration object like <select options={[{label: 'React', value: 'react'}]} />.
  • You can freely rearrange the options, insert dividing <hr /> elements, or add custom styling between options without breaking functionality.

In React, the Compound Components Pattern replicates this declarative, flexible relationship for custom UI primitives (such as Tabs, Accordions, Dropdown Menus, and Radio Groups).

2. Implementing Compound Components with React Context

To build compound components, a parent component creates a React Context to share state and updater functions with its child components implicitly.

Let's build a compound Tabs component:

JSX
import React, { useState, createContext, useContext } from 'react';
 
// 1. Create Context for Tab State
const TabsContext = createContext(null);
 
// 2. Parent Compound Component
export function Tabs({ defaultActiveTab, children }) {
const [activeTab, setActiveTab] = useState(defaultActiveTab);
 
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs-container">{children}</div>
</TabsContext.Provider>
);
}
 
// 3. Child Component: Tab Button List
export function TabList({ children }) {
return <div className="tab-buttons-row" role="tablist">{children}</div>;
}
 
// 4. Child Component: Individual Tab Trigger
export function Tab({ id, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
const isActive = activeTab === id;
 
return (
<button
role="tab"
aria-selected={isActive}
className={`tab-btn ${isActive ? 'tab-btn-active' : ''}`}
onClick={() => setActiveTab(id)}
>
{children}
</button>
);
}
 
// 5. Child Component: Tab Content Panel
export function TabPanel({ id, children }) {
const { activeTab } = useContext(TabsContext);
if (activeTab !== id) return null;
 
return (
<div role="tabpanel" className="tab-content-panel">
{children}
</div>
);
}
 
// 6. Optional: Attach children as static properties for clean namespace syntax
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;

3. Beautiful, Declarative Consumer Experience

Consumers of compound components enjoy an elegant, self-describing API:

JSX
export default function CourseCurriculum() {
return (
<Tabs defaultActiveTab="frontend">
<Tabs.List>
<Tabs.Tab id="frontend">Frontend Track</Tabs.Tab>
<Tabs.Tab id="backend">Backend Track</Tabs.Tab>
<Tabs.Tab id="data">Data Science</Tabs.Tab>
</Tabs.List>
 
<Tabs.Panel id="frontend">
<h3>HTML5, Tailwind CSS, Modern React & Vite</h3>
<p>Comprehensive client-side engineering mastery.</p>
</Tabs.Panel>
 
<Tabs.Panel id="backend">
<h3>Python, Django REST Framework, PostgreSQL</h3>
<p>Scalable backend architectures and microservices.</p>
</Tabs.Panel>
 
<Tabs.Panel id="data">
<h3>Pandas, NumPy, Power BI, Statistics</h3>
<p>End-to-end data pipelines and business intelligence.</p>
</Tabs.Panel>
</Tabs>
);
}

4. Key Architectural Benefits

  • Implicit State Sharing: The consumer never has to manually wire activeTab or onClick to each individual button; the context handles it automatically.
  • Extreme Layout Flexibility: The consumer can reorder tabs, nest tabs inside custom grid wrappers, or insert header graphics between elements without breaking state sync.
  • Inversion of Control: The consumer dictates the visual hierarchy and layout, while the compound components manage interaction logic and accessibility.

Interactive Quiz

Practice Quiz

Question 1
What is the primary inspiration behind the Compound Components pattern in React?
Question 2
What React feature is typically used under the hood to share state between compound parent and child components?
Question 3
Why do libraries assign sub-components as properties on the parent (e.g. Tabs.Tab = Tab)?
Question 4
How does the Compound Components pattern improve upon rigid "prop-heavy" components like <Tabs items={dataArray} />?
Question 5
What is rendered by <Tabs.Panel id="backend"> if the active tab is "frontend"?
Next Lesson

Race Conditions and Stale Closures in useEffect

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext