---
title: Compound Components
description: Let related components share implicit state so they can be composed together as a single unit.
tokens: ~844
---

# Compound Components

Compound components are a group of components that work together to form a single UI element, sharing implicit state through a context provider. The idea mirrors native HTML — `<select>` and `<option>` are compound components: `<select>` manages the selected value, and each `<option>` registers itself without you wiring anything manually. In React, the same idea is expressed by attaching child components as static properties on a parent (`Accordion.Item`, `Accordion.Panel`), so consumers import one namespace and compose pieces freely via dot notation.

## How It Works

1. **A parent component owns the shared state** and wraps its children in a context provider.
2. **Child components consume that context** to read or update the shared state — no props need to pass between siblings.
3. **Children are attached as static properties** on the parent (`Accordion.Item = Item`), giving consumers a single import with dot-notation access to every piece.

## Example

```tsx
import {
  createContext,
  useContext,
  useState,
  type ReactNode,
} from "react";

interface AccordionContext {
  activeIndex: number | null;
  toggle: (index: number) => void;
}

const AccordionCtx = createContext<AccordionContext | null>(null);

function useAccordion(): AccordionContext {
  const ctx = useContext(AccordionCtx);
  if (!ctx) throw new Error("Accordion.* must be used within <Accordion>");
  return ctx;
}

function Accordion({ children }: { children: ReactNode }) {
  const [activeIndex, setActiveIndex] = useState<number | null>(null);
  const toggle = (index: number) =>
    setActiveIndex((prev) => (prev === index ? null : index));

  return (
    <AccordionCtx.Provider value={{ activeIndex, toggle }}>
      {children}
    </AccordionCtx.Provider>
  );
}

function Item({
  index,
  children,
}: {
  index: number;
  children: ReactNode;
}) {
  const { activeIndex, toggle } = useAccordion();
  return (
    <div>
      <button onClick={() => toggle(index)}>
        {activeIndex === index ? "▼" : "▶"} Toggle
      </button>
      {activeIndex === index && <div>{children}</div>}
    </div>
  );
}

Accordion.Item = Item;
```

Usage looks like this:

```tsx
function App() {
  return (
    <Accordion>
      <Accordion.Item index={0}>First panel content</Accordion.Item>
      <Accordion.Item index={1}>Second panel content</Accordion.Item>
      <Accordion.Item index={2}>Third panel content</Accordion.Item>
    </Accordion>
  );
}
```

Each `Accordion.Item` reads the shared active index from context and toggles itself on click. The consumer never passes state between siblings — `Accordion` handles it all internally.

## Trade-offs

| Pros                                                          | Cons                                                                          |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| Flexible composition — consumers rearrange children freely    | Requires context boilerplate (create, provider, hook)                         |
| Clean API surface — one import, dot-notation access           | Child components only work inside their parent provider; standalone use breaks |
| Implicit state sharing — no prop drilling between siblings    | Dot-notation assignment is not tree-shakeable                                 |