skip to content

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?

level: middleimportance: should knowfreq 46%

answer

  1. one hole versus several
  2. name the region, don't guess it
  3. position breaks on any wrapper
  4. type matching breaks on any wrapper
  5. children stays for the main region

basics

~20 s

Named 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 s

Give 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 lines
jsx
function 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context