children is an ordinary prop. The only thing special about it is the syntax: whatever you put between a component’s opening and closing tags becomes props.children. Everything else follows from it being a normal value, and understanding that is what unlocks composition.
The reason it matters more than the syntax suggests is that children is the escape hatch from configuration. A component that accepts children lets the caller decide what goes inside, which removes the pressure to add a prop for every possible variation — the pressure that turns a clean component into one with eleven booleans.
Table of contents
- It is just a prop
- The boolean-prop smell
- Multiple slots
- Why you should not inspect children
- Typing children in TypeScript
- Where composition actually pays
- How this fits the rest of the stack
- FAQ
It is just a prop
These two are identical:
<Card>Hello</Card>
<Card children="Hello" />
Nobody writes the second form, but knowing they are the same explains a lot of behaviour. children can be a string, a number, an element, an array of elements, a function, null, or undefined — because props can be any of those things.
A component that renders children is trivially simple:
function Card({ children }) {
return <div className="card">{children}</div>;
}
That component has one prop and imposes nothing on what goes inside it. Compare it with the version that grows from trying to anticipate every use: title, subtitle, showFooter, footerText, variant, isCompact. Each one made sense when it was added, and together they form an API that is harder to use than writing the markup directly.
The boolean-prop smell
The signal that a component wants children instead of props is a growing pile of flags:
<Modal
title="Confirm"
showCloseButton
showFooter
confirmLabel="Delete"
cancelLabel="Keep"
isDangerous
hideOverlay
/>
Every one of those props exists to control something the caller could have provided directly. The composed version:
<Modal>
<Modal.Header>Confirm</Modal.Header>
<Modal.Body>This cannot be undone.</Modal.Body>
<Modal.Footer>
<Button variant="danger">Delete</Button>
<Button variant="ghost">Keep</Button>
</Modal.Footer>
</Modal>
This is longer at the call site, which is the objection people raise, and it is worth it for a specific reason: the next requirement does not need a new prop. A modal with three buttons, or a form in the footer, or an icon in the header, all work without touching Modal at all.
The test for whether a prop should be children: if it exists to control the presence or content of something visual, it probably wants to be a slot. If it controls behaviour, it is genuinely a prop.
Multiple slots
children is one slot. When you need several, pass elements as named props — they are the same thing without the syntactic sugar:
function Layout({ sidebar, header, children }) {
return (
<div className="layout">
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}
<Layout
header={<Nav />}
sidebar={<Filters />}
>
<Results />
</Layout>
This is the pattern that scales furthest with the least machinery. It is explicit about which slot is which, it type-checks properly, and there is no runtime inspection of the children array.
The alternative — compound components, where Modal.Header and friends are separate components the parent finds by inspecting children — reads more elegantly at the call site and costs more in implementation complexity. Choose it when the grouping genuinely matters to the reader, not by default.
Why you should not inspect children
React provides a Children utility with map, forEach, count, and toArray. It exists mostly for library authors, and reaching for it in application code is usually a sign the design took a wrong turn.
The React documentation is explicit that children should be treated as an opaque data structure. Code that iterates children and behaves differently based on what it finds is fragile in specific ways:
- It breaks when someone wraps a child in a fragment or a context provider.
- It breaks when a child is conditionally rendered and becomes null.
- It breaks when a child is a custom component that happens to render the expected one.
- It gives confusing errors, because the failure is a type check inside a parent the developer was not editing.
The alternative is almost always named props for the slots you care about. If Tabs needs to know about its tab list, take a tabs prop rather than filtering children for elements of type Tab.
One legitimate use remains: cloneElement to inject props into children is how several established libraries handle things like accessible ids across a compound component. It works. It is also the part of those libraries that is hardest to debug, which is a reasonable reason to avoid writing it yourself.
Typing children in TypeScript
The type you want in nearly every case is ReactNode:
import type { ReactNode } from 'react';
interface CardProps {
children: ReactNode;
}
function Card({ children }: CardProps) {
return <div className="card">{children}</div>;
}
ReactNode covers everything React can render: elements, strings, numbers, arrays, fragments, null, undefined, and booleans. That breadth is correct — a component that renders children should accept anything renderable.
Two notes on types that changed. React 18 removed children from the implicit props type, so a component that takes children must declare it. And PropsWithChildren still exists but adding children to your props interface directly is clearer, so most codebases have moved away from it.
Use ReactElement instead of ReactNode only when you genuinely need a single element — for a component that clones its child to add props, for example. It is a real restriction on callers, so apply it deliberately.
Where composition actually pays
The concrete win is not aesthetic. Components that take children have fewer reasons to change, which means fewer merge conflicts, fewer regressions, and fewer conversations about whether a new prop belongs.
There is a performance dimension too, and it is subtler than it looks. When you pass children from a parent, those elements are created in the parent’s render. If the intermediate component re-renders because its own state changed, the children elements are the same object references, so React can skip re-rendering them. Configuration props do not get this — a new element created inside the intermediate component on every render always re-renders.
That effect is worth having and is not usually the reason to compose. The reason to compose is that the component stops needing to know what it contains, and a component that knows less has fewer ways to be wrong.
How this fits the rest of the stack
Composition is one of those choices that costs a little more to write and considerably less to maintain, which is the same trade as most infrastructure decisions — a bit more structure up front, far less to unpick later. When a React app moves from a prototype to something with real users, the questions shift from component APIs to build output, routes, and where the data lives. Node services on RunxBuild covers deploying the server side of that from a repository, and if you are sizing a frontend, an API, and a database together, the RunxBuild hosting calculator shows them as separate line items rather than one number.
Useful related references:
- Generate a PDF from a React App: A Practical Walkthrough
- Deploy a React App for Free on RunxBuild: 2026 Guide
- Run React App: Local Preview, Production Build, and the Part People Skip
- Services on RunxBuild
FAQ
Is children a special prop in React?
Only in syntax. Whatever appears between a component’s tags is passed as props.children, but it behaves like any other prop — you could pass children as an explicit attribute and get identical behaviour.
What type should children have in TypeScript?
ReactNode in almost every case. It covers elements, strings, numbers, arrays, fragments, null, and undefined. Use ReactElement only when you genuinely need a single element, such as when cloning it to inject props.
Should I use React.Children.map?
Rarely in application code. It breaks when children are wrapped in fragments or conditionally null, and produces confusing errors. Named props for the slots you care about are more robust and easier to type.
When should I use children instead of props?
When a prop exists to control the presence or content of something visual. Props that control behaviour should stay props. A growing pile of boolean flags is the usual signal that a slot is wanted.
Does passing children improve performance?
It can. Elements passed as children are created in the parent’s render, so they keep the same object reference when an intermediate component re-renders and React can skip them. It is a real effect but a secondary reason to compose.