Children Prop Patterns
Using children and slot props for flexible APIs: wrappers, compound components, and when to prefer explicit props.
- react
- children-prop
children is just a prop — the one JSX fills automatically between open and close tags. Mastering it is how you build layout shells, providers, and compound components without prop-drilling every leaf.
Docs: Passing JSX as children, React.Children (use sparingly).
Basic wrapper
function Bleed({ children }) {
return <div className="bleed">{children}</div>;
}
<Bleed>
<img src="/hero.jpg" alt="" />
</Bleed>
The parent owns content; the child owns chrome. This is the default composition move (composition vs inheritance).
Function-as-children (render props light)
function When({ condition, children }) {
if (!condition) return null;
return typeof children === 'function' ? children() : children;
}
<When condition={ready}>{() => <Chart data={data} />}</When>
Prefer explicit renderItem props when the API is public — clearer than overloading children types. See render props.
Compound components
const TabsContext = createContext(null);
function Tabs({ children, value, onChange }) {
return (
<TabsContext.Provider value={{ value, onChange }}>
<div>{children}</div>
</TabsContext.Provider>
);
}
function TabList({ children }) {
return <div role="tablist">{children}</div>;
}
function Tab({ id, children }) {
const ctx = useContext(TabsContext);
const selected = ctx.value === id;
return (
<button
type="button"
role="tab"
aria-selected={selected}
onClick={() => ctx.onChange(id)}
>
{children}
</button>
);
}
Tabs.List = TabList;
Tabs.Tab = Tab;
Usage reads like markup:
<Tabs value={tab} onChange={setTab}>
<Tabs.List>
<Tabs.Tab id="a">A</Tabs.Tab>
<Tabs.Tab id="b">B</Tabs.Tab>
</Tabs.List>
</Tabs>
Server/client boundary note
In RSC trees, children is how a client shell receives server content without importing server modules into the client bundle. See client component boundaries.
// ClientShell.tsx
'use client';
export function ClientShell({ children }: { children: React.ReactNode }) {
return <div className="shell">{children}</div>;
}
Avoid overusing React.Children map/clone
// Fragile: breaks on fragments, conditional children, wrappers
Children.map(children, (child) =>
isValidElement(child) ? cloneElement(child, { size: 'sm' }) : child
);
Prefer context or explicit props. Cloning to inject props is a last resort and hard to type.
Interview out-loud
“children is a prop for composition. I use it for layout shells and compound components with context. Function-as-children works but explicit render props are clearer for public APIs. I avoid React.Children cloneElement when context will do.”
Related on this site
- Composition vs Inheritance
- Avoid Prop Drilling with Composition
- Layout Components
- Client Component Boundaries
Further reading
Production checklist
- Source of truth clear for every piece of UI state?
- Remount, route change, and Strict Mode cleanup paths handled?
- Urgent updates separated from deferrable work?
- Profiled before memo, virtualization, or context splits?
- Keyboard, focus, and accessible names still work after the change?
Edge cases worth rehearsing
Interviewers and production incidents cluster around the same edges: first render versus update, empty and loading states, Strict Mode double setup, concurrent interruptions, and what happens when identity (key, route params, user id) changes mid-edit. Walk one concrete user journey end-to-end — open, edit, navigate away, come back — and say which state survives.
Prefer fixing data flow and ownership before reaching for memoization or micro-optimizations. Prefer event handlers over effects when a user action is the trigger. Prefer deriving values during render over mirroring props into state. Prefer stable list keys from business ids. Measure with the profiler when performance is the claim.
When you cite an API, mention one failure mode: abort on unmount, serializable props across server/client boundaries, focus restoration for dialogs, or cache invalidation after a mutation. Specific beats generic every time.
Related guides
- useCopyToClipboard HookCopy text with navigator.clipboard, fallback execCommand, transient copied state, and permissions that fail in insecure contexts.
- useDebouncedValue HookDebounce a changing value in React without firing effects on every keystroke. Cleanup, leading vs trailing, and the stale timer that updates unmounted state.
- useEventListener HookAttach a window, document, or element listener with a stable handler ref so you do not rebind on every render. Capture option and target changes.
- useFetch With AbortControllerFetch in an effect with a signal, ignore aborted errors, and prevent the slow response from winning a race after the url changes.
- useIdle HookDetect idle users: reset a timer on input events, fire idle after timeout, and decide whether mount starts the timer immediately.