Compound Components with Context
Share implicit state between a parent component and its designated children using Context, modelling APIs like and .
Compound Components with Context is a free Frontend Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Frontend Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Are Compound Components?
Compound components are a set of related components designed to work together as a single unit — like HTML's <select> + <option>. The parent shares implicit state with its specific children.
The Goal: Cleaner APIs
Instead of <Select options={[...]} value={x} onChange={...} />, you write <Select><Option value="a">Apple</Option></Select>. The structure is declarative and JSX-native.
Naive Approach (Problem)
Passing state through props requires the consumer to wire everything. Children don't automatically know about parent state.
// Without compound pattern — verbose:
<Tabs activeIndex={0} onChange={setIndex}>
<TabList>
<Tab index={0}>Home</Tab>
<Tab index={1}>Profile</Tab>
</TabList>
</Tabs>Context as the Connector
The parent creates a Context with the shared state and provides it. Children consume the context — no explicit prop wiring.
import { createContext, useContext, useState } from 'react';
const TabsContext = createContext(null);
export function Tabs({ children, defaultIndex = 0 }) {
const [activeIndex, setActiveIndex] = useState(defaultIndex);
return (
<TabsContext.Provider value={{ activeIndex, setActiveIndex }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}Child Components Consume
Each child reads from context — no props passing down through the tree.
export function Tab({ index, children }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
const isActive = activeIndex === index;
return (
<button
className={isActive ? 'active' : ''}
onClick={() => setActiveIndex(index)}
>
{children}
</button>
);
}
export function Panel({ index, children }) {
const { activeIndex } = useContext(TabsContext);
if (activeIndex !== index) return null;
return <div className="panel">{children}</div>;
}Static Properties for Discoverability
Attach the subcomponents to the parent so users discover them via auto-complete.
Tabs.Tab = Tab;
Tabs.Panel = Panel;
// Usage:
<Tabs>
<Tabs.Tab index={0}>Home</Tabs.Tab>
<Tabs.Tab index={1}>Profile</Tabs.Tab>
<Tabs.Panel index={0}>Home content</Tabs.Panel>
<Tabs.Panel index={1}>Profile content</Tabs.Panel>
</Tabs>Real Example: Disclosure
A disclosure widget has Trigger and Panel children that share an open/closed state via context.
function Disclosure({ children }) {
const [isOpen, setIsOpen] = useState(false);
return (
<DisclosureContext.Provider value={{ isOpen, setIsOpen }}>
<div>{children}</div>
</DisclosureContext.Provider>
);
}
function Trigger({ children }) {
const { isOpen, setIsOpen } = useContext(DisclosureContext);
return <button onClick={() => setIsOpen(o => !o)}>{children}</button>;
}
function Panel({ children }) {
const { isOpen } = useContext(DisclosureContext);
return isOpen ? <div>{children}</div> : null;
}Hooks as the Public API
Export a custom hook so users can build their own components on top of yours.
export function useTabs() {
const ctx = useContext(TabsContext);
if (!ctx) throw new Error('useTabs must be used inside <Tabs>');
return ctx;
}Polymorphic Compound Components
Let parents render any element via an as prop or asChild pattern (Radix UI's approach). Lets compound components compose with arbitrary JSX.
Headless Compound Patterns
Headless libraries (Radix, Headless UI) ship compound components with no styling — accessibility logic baked in, you style as needed.
Pitfall: Children Inspection
An older pattern used React.Children to inspect children — fragile and brittle. Always prefer context for state sharing.
Quick Check
In a compound component pattern, how do child components communicate with their parent?
Recap: Compound Components
Compound components let related parts work as one declarative unit. Use Context for sharing state — Provider in the parent, useContext in children. Attach subcomponents as static properties for discoverability. Export a custom hook as the public API. Radix and Headless UI are great references. Prefer context over Children inspection.
Frequently asked questions
Is the “Compound Components with Context” lesson free?
Yes — the full text of “Compound Components with Context” is free to read here on the web, and the Frontend Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Frontend Academy course, upgrade to CoddyKit PRO.
What will I learn in “Compound Components with Context”?
Share implicit state between a parent component and its designated children using Context, modelling APIs like and . You practise Frontend Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Frontend Academy?
No prior experience is required. Frontend Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Compound Components with Context” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Frontend Academy lesson?
Yes. Every Frontend Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Compound Components with Context
- Render Props and HOC Patterns
- Portals for Modals and Tooltips
- Error Boundaries