The children Prop
Every React component automatically receives a special prop called children. Unlike other props — which you pass as JSX attributes — you pass children by nesting content between the opening and closing tags of a component. This mirrors the natural parent-child relationship of HTML.
// You pass children between the tags, not as an attribute: <Card> <h2>Hello</h2> <p>This is the card content.</p> </Card>
Inside the Card component, props.children (or the destructured children prop) holds whatever JSX or text you placed between the tags. The component can render it anywhere it likes.
A Simple Card Component
function Card({ children }) {
return (
<div
style={{
border: '1px solid #ddd',
borderRadius: 8,
padding: 16,
margin: 8,
}}
>
{children}
</div>
)
}
// Any JSX can be passed as children:
function App() {
return (
<Card>
<h2>React Tips</h2>
<p>Props flow from parent to child.</p>
<button>Learn more</button>
</Card>
)
}The ReactNode Type
When writing TypeScript, type children as ReactNode — the broadest type that covers everything React can render:
import { ReactNode } from 'react'
interface CardProps {
children: ReactNode
title?: string
}
function Card({ children, title }: CardProps) {
return (
<div className="card">
{title && <h3>{title}</h3>}
<div className="card-body">{children}</div>
</div>
)
}Layout / Wrapper Components
The children pattern shines for layout components — components whose job is to provide structure, spacing, or a visual shell around whatever content gets passed in. The layout component does not need to know anything about the content:
// A page layout wrapper
function PageContainer({ children }) {
return (
<main style={{ maxWidth: 960, margin: '0 auto', padding: '0 16px' }}>
{children}
</main>
)
}
// A two-column layout
function TwoColumnLayout({ sidebar, children }) {
return (
<div style={{ display: 'flex', gap: 24 }}>
<aside style={{ width: 240, flexShrink: 0 }}>{sidebar}</aside>
<section style={{ flex: 1 }}>{children}</section>
</div>
)
}
// Usage:
function Dashboard() {
return (
<PageContainer>
<TwoColumnLayout sidebar={<Navigation />}>
<h1>Welcome back</h1>
<RecentActivity />
</TwoColumnLayout>
</PageContainer>
)
}A Modal Component Using children
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null
return (
<div className="overlay" onClick={onClose}>
<div
className="modal"
onClick={(e) => e.stopPropagation()}
>
<button className="close-btn" onClick={onClose}>
✕
</button>
{children}
</div>
</div>
)
}
// The Modal does not care what content goes inside it:
function App() {
const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(true)}>Open Modal</button>
<Modal isOpen={open} onClose={() => setOpen(false)}>
<h2>Confirm Action</h2>
<p>Are you sure you want to delete this item?</p>
<button onClick={() => setOpen(false)}>Cancel</button>
<button onClick={handleDelete}>Delete</button>
</Modal>
</>
)
}Multiple Children and Text
children can be a single element, multiple elements, plain text, a number, or even null. React handles all of these:
// Single element child
<Card><p>One paragraph</p></Card>
// Multiple children — React stores them as an array
<Card>
<h2>Title</h2>
<p>Paragraph one</p>
<p>Paragraph two</p>
</Card>
// Plain text child
<Badge>New</Badge>
// Number child
<Badge>{42}</Badge>
// No children — children is undefined
<Card />The React.Children API
When you need to inspect or transform children programmatically, React provides the React.Children utility. The most useful methods are React.Children.count(), React.Children.map(), and React.Children.toArray():
import React from 'react'
// A Tabs component that wraps each child in a tab panel:
function Tabs({ children }) {
const [active, setActive] = useState(0)
const childArray = React.Children.toArray(children)
return (
<div>
{/* Tab buttons */}
<div role="tablist">
{childArray.map((child, i) => (
<button
key={i}
role="tab"
aria-selected={i === active}
onClick={() => setActive(i)}
>
{child.props.label}
</button>
))}
</div>
{/* Active panel */}
<div role="tabpanel">{childArray[active]}</div>
</div>
)
}
// A Tab component that is just a data holder:
function Tab({ label, children }) {
return <div>{children}</div>
}
// Usage:
<Tabs>
<Tab label="Overview">...</Tab>
<Tab label="Details">...</Tab>
<Tab label="Reviews">...</Tab>
</Tabs>Why the children Pattern Is Powerful
Inversion of control — the parent decides what content goes inside; the wrapper component just provides the shell
Decoupling —
Card,Modal, andPageContainerwork with any content without importing or knowing about itComposition over configuration — instead of passing a dozen props describing what to render, you just nest the elements you want
Reuse without inheritance — layout patterns compose freely without class inheritance
TypeScript-friendly —
ReactNodeaccurately represents everything React can render