props.children Patterns
Every React component automatically receives a special prop called children: the JSX nested inside its tags. children is what makes components composable.
The basic case
function Card({ children }) {
return <div className="card">{children}</div>;
}
<Card>
<h2>Title</h2>
<p>Body</p>
</Card>The <h2> and <p> are passed as children and rendered inside the div. The Card component doesn’t know or care what’s inside.
Why this is powerful
Without children, you’d have to keep adding props every time someone wanted a slightly different Card:
<Card title="..." body="..." footer="..." /> // ad infinitumWith children, the component owns the shell (the styled wrapper) and the caller owns the contents. That separation is one of the most reused patterns in React.
Pattern 1: Layout component
The most common use. A component that imposes a visual or structural shell:
function PageShell({ children }) {
return (
<div className="page">
<Header />
<main>{children}</main>
<Footer />
</div>
);
}Pattern 2: Multiple slots via named props
Sometimes you want more than one “children-like” hole. Don’t try to type-tag children — just use named props:
function Modal({ header, children, footer }) {
return (
<div className="modal">
<header>{header}</header>
<div className="body">{children}</div>
<footer>{footer}</footer>
</div>
);
}
<Modal
header={<h2>Confirm</h2>}
footer={<button>OK</button>}
>
Are you sure?
</Modal>Pattern 3: Render-prop / function-as-children
When the parent needs to pass data down to whatever it renders, children can be a function instead of JSX:
function Toggle({ children }) {
const [on, setOn] = useState(false);
return children({ on, toggle: () => setOn(!on) });
}
<Toggle>
{({ on, toggle }) => (
<button onClick={toggle}>{on ? 'On' : 'Off'}</button>
)}
</Toggle>Hooks have largely replaced this pattern (useToggle() is simpler), but you’ll still see it in older codebases or in cases where the rendered tree depends on the parent’s state.
Pattern 4: Compound components
Components that only make sense together, exposed as namespaced parts:
<Tabs>
<Tabs.List>
<Tabs.Trigger value="a">A</Tabs.Trigger>
<Tabs.Trigger value="b">B</Tabs.Trigger>
</Tabs.List>
<Tabs.Panel value="a">…</Tabs.Panel>
</Tabs>Internally, Tabs shares state with its parts via context. Common in component libraries (Radix, Headless UI). Probably overkill for your own components until you’ve felt the pain that motivates them.
When not to use children
If the component renders a fixed structure with no slots, just use ordinary props:
function Avatar({ src, alt }) {
return <img className="avatar" src={src} alt={alt} />;
}Reaching for children here would create a hole that doesn’t need to exist.