Type the children prop with React.ReactNode when a component accepts any JSX content, or React.ReactElement when it should receive only elements. ReactNode is the broad, safe default for most wrappers. The type only controls what TypeScript accepts, not what the component renders.
Use ReactNode for flexible children
ReactNode covers every value that can appear between JSX tags: text, numbers, elements, arrays, and null. It is the right type for most wrapper components:
interface CardProps {
children: React.ReactNode;
}
function Card({ children }: CardProps) {
return (
<section>
{children}
</section>
);
}The Card accepts a string, an element, or nothing as children. TypeScript lets all of those pass because ReactNode includes every possible child. This is why most design system wrappers type children as ReactNode.
Use ReactElement for elements only
ReactElement is narrower. It accepts JSX elements but rejects strings and numbers:
interface RowProps {
icon: React.ReactElement;
children: React.ReactNode;
}Typing icon as ReactElement signals that the slot must be an element, not plain text. Choose it when a slot only makes sense with an actual element. ReactElement also includes arrays of elements, while excluding primitive text and numbers.
Children plus named slots
A component often mixes children with other slots. The icon is a named slot, and children holds the main content:
interface AlertProps {
icon: React.ReactElement;
children: React.ReactNode;
}
function Alert({ icon, children }: AlertProps) {
return (
<div className="alert">
{icon} {children}
</div>
);
}The named slot uses ReactElement because an icon should be an element, while children stays broad. This split is the common pattern for wrapper components, and it keeps each slot's intent readable at the call site.
What TypeScript cannot do
You cannot use TypeScript to require children of a specific element type, such as only li children. ReactNode and ReactElement are the two practical levels, and there is no type for a specific tag.
Keep children typed broadly and validate any stricter structure in code when it truly matters. A wrapper that is too picky about children is harder to compose. If you need a list that only accepts rows, build a dedicated Row component and compose it instead of fighting the type system.
Why the broad type is usually right
Most wrappers should accept anything. A Card that only accepts elements forces every caller to wrap a plain string in an element, which is noise. ReactNode avoids that friction.
Reserve ReactElement for slots where an element is genuinely required, like an icon or a badge that must render markup. The distinction is about intent, not safety.
What to learn next
See the broader props typing patterns in how to type React props, then revisit the children prop guide for the runtime composition side.
Rune AI
Key Insights
- ReactNode accepts any JSX child content.
- ReactElement accepts only JSX elements.
- Use ReactNode for most wrappers.
- Use ReactElement for slots that need an element.
- TypeScript cannot require specific element children.
Frequently Asked Questions
What is the difference between ReactNode and ReactElement?
Can I require specific element children, like only li elements?
Which type should I use for children?
Conclusion
Type the children prop with React.ReactNode for any JSX content and React.ReactElement for elements only. ReactNode is the broad default, and TypeScript cannot restrict children to a specific element type.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.