Compound Components
Master the Compound Components pattern — a powerful React pattern for building flexible, expressive component APIs. Learn how to build components like Select, Tabs, and Accordion that feel native.
Last reviewed: January 2025
🔍 What Is It?
Compound Components are a set of components that share state implicitly:
// The compound component API
<Tabs defaultValue="overview">
<Tabs.List>
<Tabs.Tab value="overview">Overview</Tabs.Tab>
<Tabs.Tab value="claims">Claims</Tabs.Tab>
<Tabs.Tab value="documents">Documents</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="overview"><OverviewContent /></Tabs.Panel>
<Tabs.Panel value="claims"><ClaimsContent /></Tabs.Panel>
<Tabs.Panel value="documents"><DocumentsContent /></Tabs.Panel>
</Tabs>The parent (Tabs) owns the state (which tab is active). The children (Tabs.Tab, Tabs.Panel) read and update that state via Context — without any props being passed explicitly.
🎯 Why Do We Need It?
Without compound components, you would build a Tabs component like this:
// Prop-based API — rigid and hard to customise
<Tabs
tabs={[
{ label: 'Overview', content: <OverviewContent /> },
{ label: 'Claims', content: <ClaimsContent /> },
]}
defaultTab={0}
/>Problems: you cannot customise tab rendering, add icons, or change the layout without modifying the Tabs component itself.
Compound components give consumers full control over rendering while the parent manages shared state.
🏗️ What Happens in Real Projects?
Compound components are the foundation of every design system:
- →
<Select>/<Select.Option>— like native HTML but styled - →
<Tabs>/<Tabs.Tab>/<Tabs.Panel> - →
<Accordion>/<Accordion.Item>/<Accordion.Header>/<Accordion.Body> - →
<Menu>/<Menu.Item>/<Menu.Separator> - →
<Form>/<Form.Field>/<Form.Label>/<Form.Error>
Libraries like Radix UI, Headless UI, and Reach UI are built entirely on this pattern.
⚙️ How It Works
- Parent component creates a Context and provides it to children
- Parent manages shared state (active tab, open accordion item)
- Child components consume the Context to read state and dispatch updates
- Child components are attached to the parent as static properties (
Tabs.Tab)
Static properties pattern:
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;This groups related components under one namespace and makes the API discoverable.
💻 Example
Building a Tabs Component — Complete compound component with Context and static properties.
🤔 Common Confusions
❓ When should I use compound components vs a single component with many props?
Use compound components when: the component has multiple distinct sections (list, item, panel), consumers need to customise each section independently, or the layout itself needs to be flexible. Use a single component with props for simpler cases where the structure is always the same.
❓ What is the difference between compound components and render props?
Render props give the consumer control over rendering by passing a function. Compound components give the consumer control by providing sub-components that can be arranged freely. Compound components result in more readable, HTML-like JSX. Render props are better when the rendering logic is complex or data-dependent.
🚨 Common Production Issues
**Missing context error** — Using a sub-component outside the parent throws the context error. Always throw a descriptive error in the context hook: "Tabs.Tab must be used within <Tabs>".
**Accessibility** — Compound components need proper ARIA attributes: role="tablist", role="tab", aria-selected, role="tabpanel". Test with a screen reader.
✅ Best Practices
Throw a descriptive error in the context hook if sub-components are used outside the parent
Add ARIA attributes for accessibility — compound components often map to ARIA patterns
Use TypeScript to type the Context value and each sub-component's props
Attach sub-components as static properties for a discoverable API
Allow controlled mode (value + onChange props) alongside uncontrolled (defaultValue)
🏛️ Architect's Note
Compound components are the gold standard for design system component APIs. They provide maximum flexibility to consumers while keeping shared state encapsulated. If you are building a component library, every complex interactive component (Tabs, Accordion, Select, Menu, Dialog) should use this pattern. Libraries like Radix UI have proven that compound components with headless (unstyled) implementations are the most reusable foundation for design systems.
💬 Frequently Asked Questions
🎯 Key Takeaways
🔗 Related Topics
🎯 Practice this topic in interviews
↗ Where this topic fits
React Technology Guide
Return to the React learning hub.
Frontend Engineer Career Path
A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.
Python Developer Career Path
A practical route through Python fundamentals, automation, backend basics, testing, projects, interview preparation, and career growth.
Sources
Sources / References
Use these React references to verify the concept in official docs and continue into the most relevant SplashRide pages.
Official documentation
Related SplashRide pages
SplashRide React learning path
Browse the full React topic collection and learning order on SplashRide.
React Interview Prep
Practice React interview questions and scenario-based answers.
Frontend Engineer Career Path
A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.
Python Developer Career Path
A practical route through Python fundamentals, automation, backend basics, testing, projects, interview preparation, and career growth.
Context API
Master React Context — the built-in solution for sharing state across the component tree without prop drilling. Learn when to use it, when not to, and how senior engineers implement it in production.
Render Props
Understand the Render Props pattern — a technique for sharing logic by passing a render function as a prop. Learn when it is still useful and how it relates to hooks and compound components.