🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Compound Components in React: Flexible Sub-Component APIs

Learn the Compound Components pattern: sharing state via Context across cooperating sub-components attached with dot notation.

Total XP: 0|💻 react XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Compound components fundamentals.

Quick Quiz //

What problem do compound components solve?


🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

A single component's prop list can explode as it needs to support more and more customization. The Compound Components pattern replaces that with several cooperating sub-components, coordinated invisibly through Context. This lesson covers building a Select-style compound component from scratch.

1The Prop Explosion Problem

A component like Select that needs custom option rendering, icons, and trigger labels quickly accumulates an unwieldy list of configuration props. The Compound Components pattern addresses this by letting consumers compose the internal structure directly as JSX, rather than configuring every variation through props on one monolithic component.

2What Compound Components Look Like

Instead of one component, several small, related components are exposed together — like Select, Select.Option, and Select.Trigger — meant to be composed as JSX by the consumer. This gives the consumer full control over structure and content, while shared state is coordinated behind the scenes.

3Sharing State with Context

Sub-components need to coordinate — an Option needs to know the currently selected value and how to change it — without the consumer manually threading that data through props. The parent component creates a Context and provides shared state and setters, which each sub-component reads internally.

4Consuming Context Inside a Sub-Component

Each sub-component calls useContext to read the shared state and update functions provided by the parent. This coordination is invisible to the consumer, who only writes plain JSX, while internally it's exactly what lets the separate sub-components cooperate correctly.

5Attaching Sub-Components with Dot Notation

Assigning Select.Option = Option attaches the Option component as a property of the Select function, making <Select.Option> valid JSX. This groups related components under a single, discoverable namespace and lets consumers import just the parent component to access everything.

6Step-by-Step Breakdown

The Prop Explosion Problem. A <Select> component that needs custom option icons, custom styling per option, and a custom trigger label quickly grows an unwieldy prop list: optionIcon, optionRenderer, triggerLabel, optionClassName... Compound Components solve this by letting consumers compose the internal pieces directly, instead of configuring them all through props on one giant component.

What Compound Components Look Like. Instead of one monolithic component, you expose several small components meant to be used together, like <Select>, <Select.Option>, and <Select.Trigger>. The consumer composes them as JSX, giving them full control over structure and content, while the parent silently coordinates shared state behind the scenes.

What's the main advantage of <Select><Select.Option /></Select> over <Select options={[...]} /> for highly customizable UI?

  • Each option can be arbitrary, fully customized JSX instead of a rigid config object
  • It's always fewer total characters to type

Sharing State with Context. The sub-components need to coordinate — Select.Option needs to know the currently selected value and how to change it, without the consumer having to pass that down manually. The parent Select component creates a Context, and each child sub-component reads from it internally.

Consuming Context Inside a Sub-Component. Each sub-component, like Select.Option, calls useContext(SelectContext) to read the shared state and update function. From the consumer's perspective, this is invisible — they just write JSX. From the implementation's perspective, it's what makes the pieces actually cooperate.

How does Select.Option know which value is currently selected, without the consumer passing it a selectedValue prop directly?

  • It reads shared state from Context provided by the parent Select
  • It reads a global window variable

Attaching Sub-Components with Dot Notation. Select.Option = Option is what makes <Select.Option> valid JSX — you're attaching the Option component as a property on the Select function itself. This groups related components under a single, discoverable namespace, making the relationship between them explicit at the import site.

Mastery Achieved. You now understand the Compound Components pattern: exposing several cooperating sub-components instead of one prop-heavy monolith, sharing state invisibly through Context, and attaching sub-components via dot notation for a single clean import. Next, you'll compare controlled and uncontrolled component design more broadly.

Level Up 🚀

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Compound Components Are a Great Fit for Accessible Widget Patterns

ARIA widget patterns like listbox, tabs, and accordion involve multiple cooperating elements with specific roles and keyboard behavior — a compound component's parent can centralize that shared keyboard/focus logic once, correctly, for every sub-component that uses it.

SEO Implications

  • 1

    Compound Components Have No Direct SEO Effect

    This is purely a component API design pattern, affecting how developers compose UI, with no bearing on server-rendered content or crawlability by itself.

Best Practices

Throw a Clear Error If a Sub-Component Is Used Outside Its Parent

Have each sub-component's context read include a check that throws a descriptive error (e.g. 'Select.Option must be used inside <Select>') if the context is null, catching misuse early instead of failing silently or with a cryptic error.

Keep the Context Value Focused on What Sub-Components Actually Need

Only include the specific state and setters that sub-components genuinely read — an overly broad context value makes the pattern harder to reason about and can cause unnecessary re-renders.

Frequent Bugs

THE BUG

Using <Select.Option> outside of a <Select> parent throws a cryptic 'cannot read property of null' error.

THE FIX

Add a guard inside the sub-component's context-reading logic that throws a clear, descriptive error if the context value is null, explaining that the sub-component must be rendered inside its parent.

THE BUG

Every Option re-renders whenever any single Option's hover state changes.

THE FIX

If hover-only state is stored in the shared context, every consumer of that context re-renders on every hover change. Move state that's genuinely local to one sub-component (like hover) into that sub-component's own local state instead of the shared context.

Real-World Examples

A Compound Accordion Component

An Accordion needs multiple independently expandable sections, each needing to know whether it's currently the open one. Building it as Accordion, Accordion.Item, and Accordion.Trigger, coordinated through a shared context tracking the currently open item's id, lets consumers freely arrange and style each section's JSX.

<Accordion>
  <Accordion.Item id="faq-1">
    <Accordion.Trigger>What is React?</Accordion.Trigger>
    <Accordion.Panel>A JavaScript library for building UIs.</Accordion.Panel>
  </Accordion.Item>
</Accordion>

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Rendering a sub-component like Select.Option outside its required parent Select

function useSelectContext() { const ctx = useContext(SelectContext); if (!ctx) throw new Error('Select.Option must be used inside <Select>'); return ctx; }

The Solution //

Without a guard, reading a null context value inside the sub-component produces a confusing runtime error. Add an explicit check that throws a clear, descriptive error explaining the required parent relationship.

The Error //

Forgetting to attach a new sub-component via dot notation, breaking the single-import API

Select.Trigger = Trigger; Select.Option = Option; Select.Group = Group; // don't forget newly added sub-components

The Solution //

Every sub-component meant to be part of the public compound API needs to be explicitly assigned as a property of the parent component, or consumers won't be able to access it through the expected <Parent.Child> syntax.

Lesson Glossary

[01]Compound Components

A pattern where several cooperating sub-components are composed by the consumer instead of one heavily configured component.

Code Preview
<Select><Select.Option /></Select>

[02]Shared Context

Context created by a parent compound component to distribute state to its sub-components internally.

Code Preview
const SelectContext = createContext(null);

[03]Dot Notation Attachment

Assigning a sub-component as a property of its parent (Select.Option = Option) to enable a single import.

Code Preview
Select.Option = Option;

[04]Namespace Component

A parent component that groups related sub-components under one discoverable import.

Code Preview
import Select from './Select'

Continue Learning