DEV Community

Talisson
Talisson

Posted on

Compound Components in React: A Design System Superpower

When building component libraries or design systems, flexibility and developer ergonomics are key. You want developers to compose your components like Lego blocks, without worrying about internal state wiring.

That’s where Compound Components come in.

What Are Compound Components?

Compound Components are a React design pattern where:

  • A Parent Component (Root) holds shared state or behavior.
  • Child Components consume context from the parent, without needing to pass props manually.

It allows developers to compose UIs declaratively, while the library handles the interactions under the hood.


Real-World Examples

Library Compound Components API
Radix UI <Tabs.Root> <Tabs.List> <Tabs.Trigger> <Tabs.Content> </Tabs.Root>
React Aria Components <DialogTrigger> <Dialog> <DialogContent> </DialogTrigger>
Headless UI (Tailwind) <Menu> <MenuButton> <MenuItems> </Menu>

In all these cases, developers don’t manage state explicitly — the parent component does.


Why Compound Components?

Benefit Why It Matters
Declarative APIs Developers compose UIs by nesting components
State & Behavior Encapsulation Parent handles logic; children stay dumb
Flexible Composition Devs can reorder or style components freely
Avoid Prop Drilling No need to pass props down manually

Example: Building a Tabs Compound Component

1. Tabs Context

const TabsContext = createContext<{ value: string; setValue: (val: string) => void } | undefined>(undefined);

function useTabsContext() {
  const context = useContext(TabsContext);
  if (!context) throw new Error('Tabs components must be used within <Tabs.Root>');
  return context;
}
Enter fullscreen mode Exit fullscreen mode

2. Tabs.Root (Parent)

function TabsRoot({ children, defaultValue }: { children: ReactNode; defaultValue: string }) {
  const [value, setValue] = useState(defaultValue);

  return (
    <TabsContext.Provider value={{ value, setValue }}>
      <div>{children}</div>
    </TabsContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Tabs.Trigger (Child)

function TabsTrigger({ value: triggerValue, children }: { value: string; children: ReactNode }) {
  const { value, setValue } = useTabsContext();
  const isActive = value === triggerValue;

  return (
    <button onClick={() => setValue(triggerValue)} aria-selected={isActive}>
      {children}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Tabs.Content (Child)

function TabsContent({ value: contentValue, children }: { value: string; children: ReactNode }) {
  const { value } = useTabsContext();
  return value === contentValue ? <div>{children}</div> : null;
}
Enter fullscreen mode Exit fullscreen mode

5. Usage

<TabsRoot defaultValue="account">
  <div>
    <TabsTrigger value="account">Account</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </div>
  <TabsContent value="account">Account Content</TabsContent>
  <TabsContent value="settings">Settings Content</TabsContent>
</TabsRoot>
Enter fullscreen mode Exit fullscreen mode

Key Points:

  • Developers compose structure.
  • The library handles behavior wiring via Context.
  • Developers don’t need to pass down onClick, isActive, etc.

When Should You Use Compound Components?

Use Case Compound Components?
Complex interactions across parts Yes
State shared among siblings Yes
Simple isolated UI elements No

Best Practices

  • Use Context API to share state across compounds.
  • Ensure child components throw helpful errors if misused outside Root.
  • Document clearly how developers should compose them.
  • Provide sensible defaults (e.g., default active tab).

Conclusion

Compound Components are a superpower for design systems. They provide an intuitive API for developers, while keeping state and behavior encapsulated under the hood. It leads to better DX, more flexibility, and cleaner components.

Mastering this pattern will elevate your component library architecture.


Do you already use Compound Components? Share your experience or questions below!

Top comments (0)