Component Composition & the children Prop

Reviewed & published by Brayan K

Composition is how you build complex UIs out of small, generic pieces — by nesting them instead of bolting on more props. The key to it is the special children prop: whatever JSX you put between a component's tags is handed to that component as props.children. Master this and you'll write components that are reusable, readable, and never bloated with one-off configuration flags.

Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

1️⃣ The children Prop

When you nest content inside a component, React collects it into props.children. The component then renders {children} wherever it wants. This is the difference between a component that contains things and one that's hard-coded:

// A generic Card — it adds styling and a border, nothing more.
function Card({ children }) {
  return <div className="rounded-xl border p-4 shadow">{children}</div>;
}

// The caller decides what's inside. Same Card, different content:
function App() {
  return (
    <>
      <Card>
        <h2>Welcome</h2>
        <p>Thanks for signing up.</p>
      </Card>

      <Card>
        <img src="/avatar.png" alt="Ada" />
        <button>Follow</button>
      </Card>
    </>
  );
}

Notice Card has no idea what it's wrapping. The demo below models exactly that — a wrapper that frames whatever it's given:

// In React, <Card>...</Card> hands the inner content to Card as
// props.children. We can't render JSX here, so we model the idea:
// a "component" is a function that wraps the content it's given.

function Card(children) {
  // The card only adds a frame — it never decides WHAT goes inside.
  return "┌── card ──┐\n  " + children + "\n└──────────┘";
}

// The CALLER decides the content. Same Card, totally different uses:
console.log(Card("Welcome back, Ada!"));
console.log(Card("Your invoice is ready."));

// Card knows nothing about the text — it just frames children.

2️⃣ Composition Beats Configuration

Imagine a Dialog built with props for every variation. It grows ugly fast:

// ❌ Configuration soup — a new prop for every possible need.
<Dialog
  title="Delete file?"
  body="This can't be undone."
  showIcon
  iconName="trash"
  confirmText="Delete"
  cancelText="Keep"
  confirmColor="red"
  footerAlign="right"
/>

// ✅ Composition — pass real JSX, the Dialog just provides the shell.
<Dialog>
  <Dialog.Title>Delete file?</Dialog.Title>
  <Dialog.Body>This can't be undone.</Dialog.Body>
  <Dialog.Footer>
    <button>Keep</button>
    <button className="danger">Delete</button>
  </Dialog.Footer>
</Dialog>

The composed version never needs a new prop — the caller writes whatever markup they want. Your turn: complete a tiny Panel so it renders its title and then its children:

// 🎯 YOUR TURN — fill in the ___ then press "Try it Yourself".
// A Panel should display a title, then whatever children it's given.

function Panel(title, children) {
  // 1) Return the title on one line, then the children below it.
  return title + "\n" + ___;   // 👉 the children argument
}

console.log(Panel("Settings", "Dark mode: on"));

// ✅ Expected output:
//    Settings
//    Dark mode: on

3️⃣ Named Slots

Sometimes one children isn't enough — you want distinct regions. Because props can hold any JSX, you simply add named props. Each one is a "slot":

// Each prop is a slot the caller fills with JSX.
function SplitLayout({ sidebar, content }) {
  return (
    <div className="grid grid-cols-[200px_1fr] gap-4">
      <aside>{sidebar}</aside>
      <main>{content}</main>
    </div>
  );
}

// Usage — pass elements, not just strings:
<SplitLayout
  sidebar={<NavMenu />}
  content={<Article post={post} />}
/>

The demo models a three-slot layout so you can see how each named region is filled independently:

// "Slots" = passing JSX through named props instead of one children.
// Here a Layout takes a header, a body, and a footer slot. Modeled in JS:

function Layout({ header, body, footer }) {
  return [
    "== " + header + " ==",
    body,
    "-- " + footer + " --",
  ].join("\n");
}

console.log(Layout({
  header: "Dashboard",
  body:   "You have 3 new messages.",
  footer: "© 2026 Acme",
}));

// The Layout is generic — the caller fills each named slot.
// Swap any slot's content without touching Layout itself.

4️⃣ Children as a Function (Render Props)

Here's the power move: children can be a function. The component calls it with data, and the caller decides how to render that data. This is the "render prop" pattern — composition for behavior, not just markup:

// MouseTracker owns the behavior (tracking) but not the look.
function MouseTracker({ children }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  return (
    <div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>
      {children(pos)}    
    </div>
  );
}

// The caller decides what to DO with { x, y }:
<MouseTracker>
  {({ x, y }) => <p>Cursor at {x}, {y}</p>}
</MouseTracker>

Reorder Challenge

These lines of a reusable Modal wrapper are scrambled. Put them in the correct order:

A.       <div className="backdrop">
B.   function Modal({ title, children }) {
C.         <h2>{title}</h2>
D.           {children}
E.     return (
F.         <div className="modal">
G.   }

Quick Recall

1) You write <Box>Hi</Box>. Where does the string "Hi" arrive inside Box?

As props.children. Box renders it wherever it places {children}.

2) Your Card needs a header AND a footer region. One children isn't enough — what do you do?

Use named slots: accept JSX through props like header and footer, and render each where it belongs.

3) When does it make sense for children to be a function?

When the component owns some behavior or data (mouse position, list item, drag state) and wants the caller to decide how to render it — the render-props pattern.

Common Errors (and the fix)

📋 Quick Reference

PatternSyntax
Accept childrenfunction Box({ children }) { ... }
Render children<div>{children}</div>
Named slot<Layout header={<Nav/>} />
Render prop{children(data)}
Wrap & compose<Card><Profile /></Card>

Mini-Challenge: A Reusable Alert Box

Model an Alert that prefixes its children with an icon based on a type. Make "error" show "❌" and anything else show "ℹ️", then render the message after it.

// An Alert wraps its children with an icon chosen by 'type'.
function Alert(type, children) {
  const icon = type === "error" ? "❌" : "ℹ️";
  // your code here: return icon + " " + children
}

console.log(Alert("error", "Payment failed"));
console.log(Alert("info", "Saved successfully"));

// 🎯 Expected:
//    ❌ Payment failed
//    ℹ️ Saved successfully

🎉 Lesson Complete

Practice quiz

What is the children prop in React?

  • A list of a component's child components only
  • An array of the component's state
  • Whatever JSX you nest between a component's opening and closing tags
  • A reserved word that cannot be rendered

Answer: Whatever JSX you nest between a component's opening and closing tags. children is a special prop holding whatever you nest between the tags. The component renders it with {children}.

How does a generic wrapper component display the content passed into it?

  • By rendering {children}
  • By reading props.content
  • By using document.body
  • By calling this.render()

Answer: By rendering {children}. A wrapper renders the special children prop, e.g. <div>{children}</div>, so the caller controls the content.

What does 'composition over inheritance' mean in React?

  • Extend a base component class to reuse behavior
  • Always inline all components into one file
  • Use only class components
  • Build UIs by nesting/wrapping components instead of subclassing

Answer: Build UIs by nesting/wrapping components instead of subclassing. React shares behavior by composing — wrapping and nesting components — rather than class-style inheritance.

What is a 'slot' in React terms?

  • A built-in <slot> element like Vue
  • A named prop that accepts JSX for a distinct region (e.g. header, footer)
  • A CSS grid area
  • A special hook

Answer: A named prop that accepts JSX for a distinct region (e.g. header, footer). React has no formal slot syntax; you accept JSX through named props such as header or footer to fill regions.

Why does composition often beat adding more configuration props?

  • The caller passes real JSX, so the component stays generic without a prop for every variation
  • It makes components render faster
  • It removes the need for state
  • Props are not allowed in modern React

Answer: The caller passes real JSX, so the component stays generic without a prop for every variation. Instead of a prop per variation, you accept children/slots and let the caller supply markup — fewer props, more flexibility.

In the render-props pattern, children is...

  • A string
  • An array of elements
  • A function the component calls with data
  • Always null

Answer: A function the component calls with data. children can be a function; the component calls children(data) and the caller decides how to render that data.

You write <Box>Hi</Box>. Where does the string 'Hi' arrive inside Box?

  • props.text
  • props.children
  • props.value
  • props.slot

Answer: props.children. Nested content arrives as props.children, which Box renders wherever it places {children}.

What happens if a wrapper component forgets to render {children}?

  • React throws an error
  • The children render at the top of the page
  • The component re-renders forever
  • The nested content simply does not appear

Answer: The nested content simply does not appear. If you never render {children}, the component shows its own markup but the nested content vanishes.

Your Card needs both a header AND a footer region. What is the clean approach?

  • Pass two separate children arrays
  • Use named slot props like header and footer
  • Use two nested Card components
  • Store both in component state

Answer: Use named slot props like header and footer. One children isn't enough for distinct regions; accept named props (header, footer) and render each where it belongs.

Render props were largely the precursor to which modern feature for sharing logic?

  • Portals
  • Error boundaries
  • Custom hooks
  • Suspense

Answer: Custom hooks. Custom hooks now handle most logic sharing, though render props still fit when behavior must wrap markup.

Continue this course

Related lessons