You are designing a React `PageLayout` component with distinct header, sidebar and main regions that callers fill in. Why give each region its own prop holding JSX instead of splitting `props.children` by position or by child type?
answer
- one hole versus several
- name the region, don't guess it
- position breaks on any wrapper
- type matching breaks on any wrapper
- children stays for the main region
basics
~20 sNamed slot props make each region an explicit, order-independent, type-checkable part of the API. Splitting children by position or by inspecting child types is fragile: any wrapper, fragment or conditional around a region silently breaks the layout with no error.
solid answer
~40 sGive each region a prop that holds a node — `<PageLayout header={<Header />} sidebar={<Nav />}>{main}</PageLayout>` — and keep `children` for the single primary region. The call site then names what it is filling, the order of the attributes does not matter, and TypeScript can require `header` and mark `sidebar` optional. The alternatives fail quietly. Indexing `children[0]` and `children[1]` breaks the moment someone wraps a region in a `<div>`, renders one conditionally, or nests a fragment. Scanning children for `child.type === Header` breaks as soon as a caller wraps `Header` in anything, and gives no error — the region just disappears. A useful property of slot props is that `<Nav />` in an attribute is only a description object: if `PageLayout` chooses not to render `sidebar`, the `Nav` function never runs.
code
jsx · 20 linesfunction PageLayout({ header, sidebar, children }) {
return (
<div className="page">
<header>{header}</header>
<div className="body">
{sidebar ? <aside>{sidebar}</aside> : null}
<main>{children}</main>
</div>
</div>
);
}
export default function App() {
const header = <h1>Dashboard</h1>;
return (
<PageLayout header={header} sidebar={<nav>links</nav>}>
<p>Main content</p>
</PageLayout>
);
}go deeper
Know that a prop can hold JSX, not just strings and numbers, and be able to write <Layout header={<Header />}>main</Layout> and render that prop inside the component.
Explain why positional or type-based splitting of children fails silently — wrappers, fragments and conditionals all defeat it — and why named node props are order-independent and type-checkable.
Demonstrate API judgment: pick which single region earns children, cap the number of named slots before the layout should be split, and recognise when a region needs a callback because its content depends on internal state.
Own the consequences across a design system: slot names are public API you cannot rename cheaply, and a layout that inspects child identity couples every consumer to your exact sub-components. Decide deliberately when that coupling is worth the discoverability it buys.
## The problem: one slot is not enough `children` gives a component exactly one hole. A layout has several — a header bar, a left navigation, the main document, maybe a footer. The question every component library answers eventually is: how does the caller say *which* content goes *where*? ## The recommended shape: one prop per region A React node is an ordinary value, so a prop can hold one: ```jsx function PageLayout({ header, sidebar, footer, children }) { return ( <div className="page"> <header>{header}</header> <div className="body"> {sidebar ? <aside>{sidebar}</aside> : null} <main>{children}</main> </div> {footer ? <footer>{footer}</footer> : null} </div> ); } <PageLayout header={<TopBar />} sidebar={<Nav />}> <Article id={id} /> </PageLayout> ``` The convention that carries its weight: **`children` stays reserved for the primary region**, the one a reader would think of as "the content", and secondary regions get names. That keeps the nesting syntax meaningful instead of turning every region into an attribute. What this buys you: - **Explicitness.** The call site says `sidebar={...}`. Nobody has to know that the second nested element is the sidebar. - **Order independence.** Attributes can be written in any order, and the layout — not the caller — decides the visual order. - **Optionality that type-checks.** A required `header` and an optional `sidebar` are expressible in the props type, and a missing required region is a build error rather than a blank strip. - **No traversal code.** The component never touches `Children` helpers, so there is nothing to break. - **Cheap unused slots.** `<Nav />` written in an attribute only creates a description object. If `PageLayout` renders `sidebar` only on wide screens, `Nav`'s function body simply never runs on narrow ones. ## Why positional splitting fails The tempting shortcut is to require a fixed order and index into the array: ```jsx // fragile: assumes exactly three children, in order, unwrapped const [header, sidebar, main] = children; ``` Every one of these ordinary edits breaks it silently: wrapping a region in a `<div>` for styling, rendering the sidebar conditionally (now `children` has two entries and `main` is `undefined`), nesting two elements in one region, or passing a single child (which is not an array at all, so destructuring throws). The failure mode is a blank region with no message, which is the worst kind of API failure — it looks like your CSS is wrong. ## Why type-scanning fails The more sophisticated version walks the children and dispatches on component identity: ```jsx // still fragile Children.forEach(children, child => { if (isValidElement(child) && child.type === Header) header = child; }); ``` This is better than positions, but it depends on the child element's `type` being the exact component you imported. Wrap the header in `<Suspense>`, in a permissions guard, in a memoized wrapper, or in a project-specific `<Section>` and the match fails. React's `Children` helpers also do not traverse into fragments, so a caller who groups regions in a `<>…</>` loses them. Again: no error, just missing UI. It also forces callers to import your exact sub-component, which is fine when that is the deliberate design — an implicit parent/child API of that kind is its own pattern with its own tradeoffs — but it is a heavy price for a plain layout. ## The tradeoffs of slot props They are not free: - **The call site gets denser.** Several JSX-valued attributes on one tag read worse than nested markup. If a region's content is more than a couple of lines, hoist it to a `const` first: `const header = <TopBar user={user} />;` - **Slot count creeps.** Six named regions is a signal that the layout is really two components. - **The parent owns the elements.** Content passed in an attribute is created by the caller, so the caller must have the data. When the region's content depends on state that lives *inside* the layout, a node prop cannot express that and you need an API where the layout hands values back to the caller — a different pattern. ## The rule of thumb One hole, no constraints: use `children`. Several holes with fixed identities: name them. Never derive meaning from a child's position or its component type unless you are deliberately shipping a strict, documented parent/child API and are ready to defend the coupling.
- If `PageLayout` decides not to render the `sidebar` prop on small screens, does the passed component still do work?No. Writing `<Nav />` in an attribute only creates a small object describing what to render; React calls `Nav` when it renders that element. If the layout drops the prop on the floor, `Nav` never executes and its hooks never run. Unused slot content costs an object allocation, not a render.
- When would you accept a function for a region instead of a node?When the region's content depends on values the layout itself owns — a resize observer's measurements, or an open/closed flag — the caller cannot build the element in advance, because the data does not exist at the call site. Handing the caller a callback that receives those values is the standard escape hatch; it is a different pattern with its own wrapper-nesting tradeoffs.
- How do you keep a slot-heavy call site readable?Hoist each region into a named `const` above the return and pass the variables, so the JSX tag stays one line per slot. If you still need more than about four regions, that is usually a signal the layout is doing two jobs and should be split into an outer shell plus an inner content component.
saying these in an interview costs you the question
- Destructures children by index to find each region
- Matches child.type against imported components
- Assumes children is always an array of the expected length
- Thinks JSX in a prop renders immediately
- Adds a boolean prop per region instead of a node prop