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. See the starter template: github.com/sindev08/react-principles-nextjs → src/shared/components/PageLayout.tsx and src/ui/Card.tsx

shared/components/PageLayout.tsx — from react-principles-nextjs starter
// Named slots pattern — multiple injection points via named props
interface PageLayoutProps {
  header?: React.ReactNode;   // slot for navbar/header
  sidebar?: React.ReactNode;  // slot for sidebar navigation
  children: React.ReactNode;  // main content area
}

export function PageLayout({ header, sidebar, children }: PageLayoutProps) {
  return (
    <div className="flex min-h-screen flex-col">
      {header && <header className="border-b">{header}</header>}
      <div className="flex flex-1">
        {sidebar && <aside className="w-64 shrink-0 border-r">{sidebar}</aside>}
        <main className="flex-1 p-6">{children}</main>
      </div>
    </div>
  );
}

// Usage in a Server Component page:
export default async function UsersPage() {
  return (
    <PageLayout
      header={<Navbar />}
      sidebar={<Sidebar items={menuItems} />}
    >
      {/* Client Component as children — clean server/client boundary */}
      <UserList />
    </PageLayout>
  );
}
open_in_new

View composition components in starter

View the real implementation in react-principles-nextjs

arrow_forward
menu_book
React Patterns

Helping developers build robust React applications since 2026.

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