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
- What the special children prop is and how it flows in
- Build a generic Card/Panel that wraps any content
- Pass JSX through named props as 'slots' (header, footer)
- Why composition beats piling on configuration props
- Render props: passing a function as children
- 'Composition over inheritance' — React's core philosophy
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: on3️⃣ 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)
- Forgetting to render {children}: the component shows its own markup but the nested content vanishes. Always render {children} somewhere.
- Prop explosion: if you're adding footerButtonText, footerButtonColor, etc., stop — accept a footer slot instead.
- Calling a render-prop child like markup: {children} renders an element; {children(data)} calls a function. Pick the one matching how the caller passes it.
- Reaching for inheritance: trying to extends Dialog? React doesn't work that way — wrap and compose instead.
📋 Quick Reference
| Pattern | Syntax |
|---|---|
| Accept children | function 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
- ✅ children is whatever JSX you nest between a component's tags
- ✅ Generic wrappers (Card, Modal, Panel) render {children}
- ✅ Named props act as slots for distinct regions
- ✅ Composition beats piling on configuration props
- ✅ A function child (render prop) shares behavior, not just markup
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
- Previous: TypeScript with React
- Next: Portals — Modals & Tooltips — Render outside the parent DOM tree for modals, tooltips, and overlays
- Quick reference: React cheat sheet › Components & Props