Children and Compound Components
Introduction
children lets parents pass arbitrary JSX into a component—similar to Vue’s default slot. Compound components split layout into named parts (Card, Card.Header, Card.Body) without prop explosion. This chapter covers composition patterns that replace Vue slots.
Prerequisites
children Prop
Card.tsx:
Usage:
<Card title="Settings">
<p>Notification preferences go here.</p>
<button type="button">Save</button>
</Card>Code explanation:
- Content between opening and closing tags becomes
children React.ReactNodeaccepts strings, elements, fragments, arrays
Compare Vue <slot> in Slots.
Named Regions via Props
Slot-like API without context:
<PageLayout
header={<h1>Dashboard</h1>}
sidebar={<Nav />}
>
<DashboardContent />
</PageLayout>Render Props (Pattern)
Pass a function that returns UI:
type ListProps<T> = {
items: T[];
renderItem: (item: T) => React.ReactNode;
};
export function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map((item) => <li key={String(item)}>{renderItem(item)}</li>)}</ul>;
}<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
/>Use when the parent needs flexible markup; custom hooks often replace render props today.
Compound Components
Tabs.tsx:
Usage:
<Tabs defaultTab="profile">
<Tabs.List>
<Tabs.Tab id="profile">Profile</Tabs.Tab>
<Tabs.Tab id="billing">Billing</Tabs.Tab>
</Tabs.List>
<Tabs.Panel id="profile">Profile form</Tabs.Panel>
<Tabs.Panel id="billing">Billing info</Tabs.Panel>
</Tabs>Compound components share implicit state via Context—next chapter goes deeper.
When to Split Components
| Smell | Fix |
|---|---|
| Huge JSX block | Extract Header, Footer |
| Repeated layout | PageLayout with children |
| Flexible insertion points | Named props or compound components |
FAQ
children vs props slot?
children is the default slot; extra ReactNode props are named slots.
Empty children?
children can be undefined—render {children ?? null} if needed.
Fragment as children?
Valid—parent receives multiple nodes without a wrapper.
Scoped slot equivalent?
Render prop renderItem={(item) => ...} or pass component as prop.