Himanshu SharmaSoftware engineer
Let’s talk

React · Lesson 9 of 38

The children prop and prop patterns

Use the children prop to build wrapper components, pass elements through named props as slots, forward props, and choose composition instead of inheritance.

15 minComponents and propsUpdated 24 October 2026

By the end of this lessonBuild wrapper and layout components with children, and pass JSX through props when a component needs more than one open area.

You have passed data to components as props. Props can also carry markup. This is what makes wrapper components possible: a frame, a card, a modal window or a page layout that does not know in advance what it will contain. This lesson covers children and the related patterns.

The children prop

Everything you write between a component's opening and closing tags is delivered to it as a special prop called children. The component renders it wherever it wants:

jsx
function Card({ title, children }) {
  return (
    <section style={{ border: "1px solid #999", borderRadius: 8, padding: 12, margin: "8px 0" }}>
      <h3 style={{ marginTop: 0 }}>{title}</h3>
      {children}
    </section>
  );
}

function App() {
  return (
    <div>
      <Card title="Welcome">
        <p>This paragraph is the children of the first card.</p>
      </Card>
      <Card title="Shopping list">
        <ul>
          <li>Milk</li>
          <li>Bread</li>
        </ul>
        <small>Two items</small>
      </Card>
    </div>
  );
}

export default App;
result
Welcome
This paragraph is the children of the first card.
Shopping list
Milk
Bread
Two items

Card knows nothing about paragraphs or lists. It provides the border, the padding and the title, and {children} marks the hole. The second card has two siblings between its tags, so its children is an array of two elements, and React renders both. This is how you write layout once and reuse it with different content.

What children can be

children can be nearly anything JSX can hold: nothing, text, a number, one element, or several:

jsx
function Show({ children }) {
  return <div style={{ margin: "4px 0" }}>[{children}]</div>;
}

function App() {
  return (
    <div>
      <Show>just text</Show>
      <Show>{42}</Show>
      <Show><b>bold</b> and <i>italic</i></Show>
      <Show>{["a", "b", "c"]}</Show>
      <Show></Show>
    </div>
  );
}

export default App;
result
[just text]
[42]
[bold and italic]
[abc]
[]

The empty <Show></Show> gives undefined children, and prints only the square brackets. A good wrapper handles this: you might render the frame only when children exists, or show a placeholder.

Wrapper components

Wrapper components are very common. A few shapes you will write often:

jsx
function Page({ children }) {
  return (
    <main style={{ maxWidth: 480, margin: "0 auto", fontFamily: "system-ui" }}>
      {children}
    </main>
  );
}

function Alert({ type = "info", children }) {
  const colors = { info: "#92c9ff", warning: "#ffd45c", error: "#ff7a59" };
  return (
    <div role="alert" style={{ background: colors[type], padding: "6px 10px", borderRadius: 6, margin: "6px 0" }}>
      {children}
    </div>
  );
}

function App() {
  return (
    <Page>
      <h2>Dashboard</h2>
      <Alert>Your profile is 80% complete.</Alert>
      <Alert type="warning">Your trial ends in 3 days.</Alert>
      <Alert type="error">Payment failed. <a href="#billing">Update billing</a></Alert>
    </Page>
  );
}

export default App;
result
Dashboard
Your profile is 80% complete.
Your trial ends in 3 days.
Payment failed. Update billing

Page centres its content, and Alert combines a normal prop (type) with children for the message. Notice that the message of the last alert contains a link: the wrapper does not care.

Several areas: named props as slots

children gives a component one open area. What if a layout has a header, a sidebar and a main area? Pass JSX through named props. A prop can hold any value, including a React element:

jsx
function Layout({ header, sidebar, children }) {
  return (
    <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 8, border: "1px solid #999", padding: 8 }}>
      <div style={{ gridColumn: "1 / -1", background: "#eee", padding: 4 }}>{header}</div>
      <aside style={{ background: "#f6f6f6", padding: 4 }}>{sidebar}</aside>
      <main>{children}</main>
    </div>
  );
}

function App() {
  return (
    <Layout
      header={<strong>My App</strong>}
      sidebar={<ul style={{ margin: 0, paddingLeft: 16 }}><li>Home</li><li>Settings</li></ul>}
    >
      <h3>Main content</h3>
      <p>This goes in the main area.</p>
    </Layout>
  );
}

export default App;
result
My App
Home
Settings
Main content
This goes in the main area.

The element passed as header={<strong>My App</strong>} is already created by the parent; Layout just decides where to put it. Think of the props as named slots (a word borrowed from other frameworks). Use children for the main slot, and named props for the extras.

Functions as children (render props)

Sometimes the wrapper owns some data and the parent decides how to display it. Passing a function gives the parent that control: the wrapper calls it with the data. This pattern is called a render prop.

jsx
function List({ items, renderItem }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

function App() {
  const people = [
    { id: 1, name: "Asha", age: 30 },
    { id: 2, name: "Ravi", age: 25 },
  ];
  return (
    <div>
      <List items={people} renderItem={p => <span>{p.name} ({p.age})</span>} />
      <List items={people} renderItem={p => <b>{p.name.toUpperCase()}</b>} />
    </div>
  );
}

export default App;
result
Asha (30)
Ravi (25)
ASHA
RAVI

List loops and wraps each item in an li; the caller says how one item looks. Render props were once the main way to share logic between components. Today custom hooks usually do that job better (see Custom hooks), but you will meet render props in libraries, and they remain useful for flexible list and table components.

Forwarding props to an element

A wrapper often needs to accept extra props and pass them to the HTML element underneath, so it behaves like the real element. Collect the rest with ...rest:

jsx
function Button({ variant = "plain", children, ...rest }) {
  const style = variant === "primary"
    ? { background: "#4b3df2", color: "white", border: 0 }
    : { background: "white", border: "1px solid #999" };
  return (
    <button style={{ padding: "6px 12px", borderRadius: 6, marginRight: 6, ...style }} {...rest}>
      {children}
    </button>
  );
}

function App() {
  return (
    <p>
      <Button>Plain</Button>
      <Button variant="primary" title="Saves your work">Save</Button>
      <Button disabled>Disabled</Button>
    </p>
  );
}

export default App;
result

title and disabled were not named in Button, yet they reach the real <button> through ...rest. This keeps your design-system components as flexible as the HTML they wrap.

Composition instead of inheritance

Programmers coming from object-oriented languages ask how to make a PrimaryButton that "extends" Button. In React you do not inherit; you compose. A PrimaryButton is simply a component that renders a Button with some props already filled in:

jsx
function PrimaryButton(props) {
  return <Button variant="primary" {...props} />;
}

A Dialog that is "a special kind of Panel" is a Panel rendered inside a Dialog function. Composition with props and children is flexible enough that the React team has never found a case that required inheritance. It also keeps components small and easy to follow.

Common mistakes

  • Forgetting to render {children} in the wrapper, so the content never appears.
  • Trying to pass children as an attribute (<Card children="..." />). It works but is unusual; write it between the tags.
  • Calling a render prop incorrectly: pass the function (renderItem={fn}), then call it inside (renderItem(item)).
  • Spreading ...rest before your own props, which lets callers override what you set. Put the spread where you intend (after, to allow overrides; before, to prevent them).
  • Making wrappers that know too much about their content. If Card checks what its children are, the design is probably wrong.

Exercise

  1. Build a Modal component with a title prop and children, drawn as a bordered box, and use it with a form-like content.
  2. Add a footer prop to Card and render it below the children when it is provided.
  3. Write IconButton({ icon, children }) that shows an emoji icon before the label.
  4. Change List so that when items is empty it shows "Nothing here" instead of an empty list.

Key points

  • Whatever you write between a component's opening and closing tags arrives as the children prop.
  • Wrapper components such as Card, Panel and Layout use children so that they do not need to know their content.
  • To fill several areas, pass JSX through named props (like header and footer) and render them where you want.
  • React favours composition: build bigger components by combining and configuring smaller ones, not by inheriting from them.

Check yourself

1. In <Card><p>Hi</p></Card>, how does Card receive <p>Hi</p>?
2. What can children contain?
3. Which approach lets a Layout have separate header and sidebar areas?
4. What do you get from <Panel>{}</Panel>?