GitHub

Component Composition

How components combine and communicate — children props, slot patterns, and why composition beats deep prop drilling.

01

Principle

Prop drilling happens when you pass data through multiple components that do not use it — just to get it to a component deep in the tree. Composition solves this differently: instead of passing data down, you pass components down. The parent controls what gets rendered, and children receive exactly what they need directly.

lightbulb

When you find yourself adding a prop to a component just to pass it further down, stop. That is the signal to use composition instead.

02

Rules

  • check_circle
    Use children for flexible contentThe children prop lets a parent inject content into a component without the component needing to know what it is.
  • check_circle
    Use named slots for multiple injection pointsWhen you need more than one place to inject content (header + footer + body), use named props instead of children.
  • check_circle
    Prefer composition over configurationA component that accepts children is more flexible than one with 10 props controlling its internals. Compose behavior, do not configure it.
  • check_circle
    Keep components focusedEach component does one thing. Composition is how you build complex UIs from simple, focused pieces.
03

Pattern

components/Card.tsx — slot composition pattern
// ❌ Prop drilling — Card needs to know about title, footer, etc.
<Card
  title="Recipe"
  subtitle="Foundations"
  footer={<Button>View</Button>}
  headerIcon="layers"
/>

// ✅ Composition — Card just provides structure
<Card>
  <Card.Header>
    <span>Foundations</span>
    <h2>Recipe</h2>
  </Card.Header>
  <Card.Body>
    Content goes here
  </Card.Body>
  <Card.Footer>
    <Button>View</Button>
  </Card.Footer>
</Card>

// The Card implementation
interface CardProps { children: React.ReactNode }
interface CardHeaderProps { children: React.ReactNode }

function Card({ children }: CardProps) {
  return <div className="rounded-xl border bg-white">{children}</div>;
}

function CardHeader({ children }: CardHeaderProps) {
  return <div className="p-4 border-b">{children}</div>;
}

Card.Header = CardHeader;
Card.Body = ({ children }: CardProps) => <div className="p-4">{children}</div>;
Card.Footer = ({ children }: CardProps) => <div className="p-4 border-t">{children}</div>;
04

Implementation

info

Version Compatibility

Requires React 19+ and the latest stable versions of all dependencies shown.

In Next.js, composition works the same way. Server Components can pass Client Components as children — this is how you keep server/client boundaries clean.

features/cookbook/components/RecipeLayout.tsx
// Server Component — fetches data
export default async function RecipePage({ params }: PageProps) {
  const detail = await getRecipeDetail(params.slug);

  return (
    // Passes a Client Component as children
    <RecipeLayout
      header={<RecipeHeader title={detail.title} />}
      sidebar={<RecipeToc sections={detail.sections} />}
    >
      {/* Client Component receives data as props, not fetching itself */}
      <RecipeContent detail={detail} />
    </RecipeLayout>
  );
}

// RecipeLayout — just structure, no data concerns
interface RecipeLayoutProps {
  header: React.ReactNode;
  sidebar: React.ReactNode;
  children: React.ReactNode;
}

export function RecipeLayout({ header, sidebar, children }: RecipeLayoutProps) {
  return (
    <div>
      <header>{header}</header>
      <div className="flex">
        <aside>{sidebar}</aside>
        <main>{children}</main>
      </div>
    </div>
  );
}
menu_book
React Patterns

Helping developers build robust React applications since 2025.

© 2025 React Patterns Cookbook. Built with ❤️ for the community.
react-principles