Back to all notes

Compound Components: Flexible APIs Built from Small Pieces

Create components that work together via context so consumers can compose them in any order.

2 min read

Introduction

Compound components are a group of components designed to work together—like Tabs, Tabs.List, and Tabs.Panel. They share context so consumers can compose them in whatever order they need.

Why this matters

Sometimes a single component needs flexible “slots” that consumers can arrange. Tabs, cards, dropdowns—these benefit from a parent component that provides context and child sub‑components that read from it.

The problem

Hard‑coding section order or passing lots of props between siblings leads to rigid, brittle APIs.

Inefficient approach

One monolithic component controls layout and content—no flexibility:

function PostCard({ post }) {
  return (
    <div className="post-card">
      <h2>{post.title}</h2>
      <p>{post.content}</p>
      <p>— {post.author}</p>
    </div>
  );
}

function App() {
  const post = {
    title: "Understanding React Patterns",
    content: "React patterns help us write better, more maintainable code...",
    author: "John Doe"
  };
  return (
    <div>
      <h1>Compound Components Pattern</h1>
      <PostCard post={post} />
    </div>
  );
}

export default App;

The solution

Provide shared data via context and expose sub‑components as static properties.

import { createContext, useContext } from "react";

const PostContext = createContext();
const usePost = () => useContext(PostContext);

function PostCard({ children, post }) {
  return (
    <PostContext.Provider value={post}>
      <div className="post-card">{children}</div>
    </PostContext.Provider>
  );
}

PostCard.Header = function PostCardHeader() {
  const post = usePost();
  return <h2>{post.title}</h2>;
};

PostCard.Body = function PostCardBody() {
  const post = usePost();
  return <p>{post.content}</p>;
};

PostCard.Footer = function PostCardFooter() {
  const post = usePost();
  return <p>— {post.author}</p>;
};

function App() {
  const post = {
    title: "Understanding React Patterns",
    content: "React patterns help us write better, more maintainable code...",
    author: "John Doe"
  };
  return (
    <div>
      <h1>Compound Components Pattern</h1>
      <PostCard post={post}>
        <PostCard.Header />
        <PostCard.Body />
        <PostCard.Footer />
      </PostCard>
    </div>
  );
}

export default App;

Step-by-step

  1. Create a context for shared data and a usePost helper.
  2. Implement PostCard to provide the post via context.
  3. Add static sub‑components (Header, Body, Footer) that read from context.
  4. Compose sub‑components in any order inside PostCard.
  5. Keep sub‑components focused on rendering; avoid state in them.

Tips

  • Keep sub‑components small and focused; they should read from context, not manage state.
  • Document which sub‑components are available and any required order (ideally none).
  • For advanced needs (controlled vs uncontrolled), consider exposing both stateful and stateless variants.

Knowledge base

Problem snippet:

<h2>{post.title}</h2>
<p>{post.content}</p>
<p>— {post.author}</p>

Solution snippet:

<PostCard post={post}>
  <PostCard.Header />
  <PostCard.Body />
  <PostCard.Footer />
</PostCard>