ESC

Type to search the knowledge base.

Children Prop Patterns

Using children and slot props for flexible APIs: wrappers, compound components, and when to prefer explicit props.

beginner3 min read
  • 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.”

Further reading

Production checklist

  1. Source of truth clear for every piece of UI state?
  2. Remount, route change, and Strict Mode cleanup paths handled?
  3. Urgent updates separated from deferrable work?
  4. Profiled before memo, virtualization, or context splits?
  5. 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