Skip to content
exercises
React

Compound-component tabs

Tabs.List, Tabs.Tab, Tabs.Panel — context inside.

IntermediateSelf

Build a Tabs component using the compound component pattern: Tabs.List, Tabs.Tab, and Tabs.Panel as co-ordinated sub-components sharing state via context.

The Pattern

Instead of a single <Tabs panels={[...]} /> with data passed as props, compound components give callers control over structure:

<Tabs defaultTab="about">
<Tabs.List>
<Tabs.Tab id="about">About</Tabs.Tab>
<Tabs.Tab id="work">Work</Tabs.Tab>
<Tabs.Tab id="contact">Contact</Tabs.Tab>
</Tabs.List>
<Tabs.Panel id="about">
<About />
</Tabs.Panel>
<Tabs.Panel id="work">
<Work />
</Tabs.Panel>
<Tabs.Panel id="contact">
<Contact />
</Tabs.Panel>
</Tabs>

The caller decides layout. The component handles the state wiring.

The Implementation

const TabsContext = createContext(null);
function Tabs({ defaultTab, children }) {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function TabList({ children }) {
return <div role="tablist">{children}</div>;
}
function Tab({ id, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
const isActive = activeTab === id;
return (
<button
role="tab"
aria-selected={isActive}
aria-controls={`panel-${id}`}
onClick={() => setActiveTab(id)}
>
{children}
</button>
);
}
function Panel({ id, children }) {
const { activeTab } = useContext(TabsContext);
if (activeTab !== id) return null;
return (
<div role="tabpanel" id={`panel-${id}`}>
{children}
</div>
);
}
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = Panel;

Why Compound Components

A prop-based <Tabs panels={data} /> locks callers into your layout. Compound components let callers render anything between <Tabs.List> and <Tabs.Panel> — extra controls, ads, whatever — without the component needing to know about it.

Why It Matters

The compound pattern is used in Radix UI, Headless UI, and React Aria. Understanding it means you can use those libraries fluently and recognize when to apply the pattern in your own work.