Children Prop in React TypeScript: Complete Guide
The children prop is React's built-in mechanism for composition—it lets you pass content into a component, from simple text to complex nested components and fragments. In TypeScript, the children prop requires a specific type annotation because it accepts many value types (strings, elements, arrays, fragments). Typing children correctly is essential for wrapper components, layouts, and design systems.
What Is the Children Prop?
The children prop contains whatever JSX is placed inside a component's opening and closing tags:
// Layout.tsx
interface LayoutProps {
children: React.ReactNode;
}
const Layout: React.FC<LayoutProps> = ({ children }) => (
<div className="layout">
<header>Welcome</header>
<main>{children}</main>
<footer>Footer</footer>
</div>
);
// Usage
<Layout>
<h1>Home</h1>
<p>Welcome to my site</p>
</Layout>
The <h1> and <p> are passed as children. The Layout component receives them in the children prop and renders them in the main area.
React.ReactNode vs. React.ReactElement
React.ReactNode and React.ReactElement are the two primary children types. Understanding the difference prevents type errors:
| Type | Accepts | Use Case |
|---|---|---|
React.ReactNode | Text, elements, arrays, fragments, null, undefined, boolean | Most flexible, for wrapper components |
React.ReactElement | Only JSX elements, not text or null | When you need guaranteed JSX, not text |
React.FC (auto-children) | Includes ReactNode children automatically | Function components with React.FC |
Using React.ReactNode
React.ReactNode accepts text, elements, arrays, fragments, and nullish values. Use it when children can be anything:
// Card.tsx
interface CardProps {
title: string;
children: React.ReactNode;
footer?: React.ReactNode;
}
const Card: React.FC<CardProps> = ({ title, children, footer }) => (
<div className="card">
<h3>{title}</h3>
<div className="card-content">{children}</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
// Valid children:
<Card title="Info">Hello world</Card>
<Card title="Info"><p>Paragraph</p></Card>
<Card title="Info"><Button label="Click" onClick={() => {}} /></Card>
<Card title="Info"><><span>Fragment</span></></Card>
<Card title="Info">{null}</Card>
<Card title="Info">{[<div key="1">Item 1</div>, <div key="2">Item 2</div>]}</Card>
Using React.ReactElement
React.ReactElement is stricter—it only accepts JSX elements:
// Wrapper.tsx
interface WrapperProps {
children: React.ReactElement;
}
const Wrapper: React.FC<WrapperProps> = ({ children }) => (
<div className="wrapper">{children}</div>
);
// Valid:
<Wrapper><div>Element</div></Wrapper>
<Wrapper><MyComponent /></Wrapper>
// Error: string is not assignable to ReactElement
<Wrapper>Text</Wrapper>
// Error: ReactElement | null is not assignable to ReactElement
<Wrapper>{null}</Wrapper>
Use ReactElement when the component logic requires a JSX element—for example, to extract props from the child.
Typing Single vs. Multiple Children
Control whether a component accepts one or many children:
// SingleChild.tsx
interface SingleChildProps {
children: React.ReactElement;
}
const SingleChild: React.FC<SingleChildProps> = ({ children }) => (
<div>{children}</div>
);
// MultipleChildren.tsx
interface MultipleChildrenProps {
children: React.ReactNode[];
}
const MultipleChildren: React.FC<MultipleChildrenProps> = ({ children }) => (
<ul>
{children.map((child, i) => (
<li key={i}>{child}</li>
))}
</ul>
);
// Usage
<SingleChild><span>One child</span></SingleChild>
// Error: two children not allowed
<SingleChild><span>One</span><span>Two</span></SingleChild>
// Valid: array of children
<MultipleChildren>{[<span key="1">One</span>, <span key="2">Two</span>]}</MultipleChildren>
For strict single-child, use React.ReactElement. For flexible multiple, use React.ReactNode[] or just React.ReactNode (which implicitly allows multiple).
Children with Specific Types
When you need children of a specific component type, use generics (see Advanced: Generic Component Props with Constraints) or type the children prop directly:
// MenuItem.tsx
interface MenuItemProps {
label: string;
}
const MenuItem: React.FC<MenuItemProps> = ({ label }) => (
<li>{label}</li>
);
// Menu.tsx
interface MenuProps {
children: React.ReactElement<MenuItemProps>[];
}
const Menu: React.FC<MenuProps> = ({ children }) => (
<ul className="menu">
{children}
</ul>
);
// Valid: Menu expects MenuItem children
<Menu>
<MenuItem label="Home" />
<MenuItem label="About" />
</Menu>
// Error: <button> is not ReactElement<MenuItemProps>
<Menu>
<MenuItem label="Home" />
<button>Invalid</button>
</Menu>
This ensures only MenuItem elements are children of Menu, catching mistakes at compile time.
Children with Fallback Values
Use optional children with a default fallback:
// Panel.tsx
interface PanelProps {
title: string;
children?: React.ReactNode;
defaultMessage?: string;
}
const Panel: React.FC<PanelProps> = ({ title, children, defaultMessage = 'No content' }) => (
<div className="panel">
<h3>{title}</h3>
{children ? <div>{children}</div> : <p className="muted">{defaultMessage}</p>}
</div>
);
// Usage
<Panel title="Info">Hello</Panel>
<Panel title="Empty" />
<Panel title="Custom Empty" defaultMessage="Loading..." />
Optional children allow the component to work with or without content.
Render Function Children (Function Children Pattern)
A function as children is a pattern where children is a function that returns JSX. This is powerful for passing state from parent to child:
// DataProvider.tsx
interface DataProviderProps {
data: string[];
children: (items: string[]) => React.ReactNode;
}
const DataProvider: React.FC<DataProviderProps> = ({ data, children }) => (
<div className="provider">
{children(data)}
</div>
);
// Usage
<DataProvider data={['Apple', 'Banana', 'Cherry']}>
{(items) => (
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
)}
</DataProvider>
Type the children function to document what arguments it receives and must return.
React.FC Automatically Includes Children
When you use React.FC, the children prop is automatically included in the type signature:
// Box.tsx - using React.FC (children auto-included)
interface BoxProps {
padding: number;
// children is automatically added by React.FC
}
const Box: React.FC<BoxProps> = ({ padding, children }) => (
<div style={{ padding }}>{children}</div>
);
// Valid—children accepted automatically
<Box padding={10}>Hello</Box>
With React.FC, TypeScript automatically includes children: React.ReactNode in the props. If you type the component manually without React.FC, you must add children to the interface explicitly.
Children with TypeScript Generics
For advanced patterns, use generics to type children that accept specific props:
// List.tsx
interface ListProps<T> {
items: T[];
children: (item: T, index: number) => React.ReactNode;
}
const List = <T,>({ items, children }: ListProps<T>) => (
<ul>
{items.map((item, i) => (
<li key={i}>{children(item, i)}</li>
))}
</ul>
);
// Usage
<List items={['Alice', 'Bob']}>
{(name) => <strong>{name}</strong>}
</List>
<List items={[{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }]}>
{(item) => <span>{item.name}</span>}
</List>
Generics let you type children functions to match the data being rendered.
Key Takeaways
React.ReactNodeis the most flexible children type, accepting text, elements, fragments, arrays, null, and undefined.React.ReactElementis stricter—use it when children must be JSX elements, not text or null.- Mark children as optional (
children?: React.ReactNode) and provide a default fallback if the component works without content. - Function children (
(data) => ReactNode) are powerful for passing parent state to child JSX. React.FCautomatically includes children in the prop interface, so you may not need to declare it explicitly.- Use generics to type children functions that depend on list item types or other dynamic data.
Frequently Asked Questions
Why does <Card>{text}</Card> show a type error when children expects React.ReactElement?
React.ReactElement only accepts JSX elements, not text. Text is React.ReactNode, which is broader. Use ReactNode for wrapper components that accept any content, or ReactElement only when your component requires actual JSX.
Is children: React.ReactNode the same as children: React.FC?
No—ReactNode is a type for the content inside JSX. React.FC is a type for a component function. You use children: React.ReactNode in a component's props interface.
Can I type children to accept only text, no JSX?
Not precisely—React doesn't distinguish between text and elements at the type level. You can type children: string, but then you lose the ability to pass JSX. It's better to accept ReactNode and document in comments that text is expected.
Why do I need to pass a key when rendering children in an array?
React uses keys to track which item is which when the list changes. Without keys, React may reuse DOM nodes incorrectly, causing state bugs. Always pass a stable, unique key (usually an id, not an array index).
What is the difference between children and other props?
Children are implicit—they come from JSX between tags. Other props are explicit attributes. Functionally, children is just another prop, but React handles it specially and React.FC includes it automatically.