ReactThe children Prop

The children Prop

Every React component automatically receives a special prop called children. Unlike other props — which you pass as JSX attributes — you pass children by nesting content between the opening and closing tags of a component. This mirrors the natural parent-child relationship of HTML.

JSX
// You pass children between the tags, not as an attribute:
<Card>
  <h2>Hello</h2>
  <p>This is the card content.</p>
</Card>

Inside the Card component, props.children (or the destructured children prop) holds whatever JSX or text you placed between the tags. The component can render it anywhere it likes.

A Simple Card Component

JSX
function Card({ children }) {
  return (
    <div
      style={{
        border: '1px solid #ddd',
        borderRadius: 8,
        padding: 16,
        margin: 8,
      }}
    >
      {children}
    </div>
  )
}

// Any JSX can be passed as children:
function App() {
  return (
    <Card>
      <h2>React Tips</h2>
      <p>Props flow from parent to child.</p>
      <button>Learn more</button>
    </Card>
  )
}
Note
`children` is just a regular prop under the hood. You could write `<Card children={<p>Hello</p>} />` and it would work the same way — but the tag-nesting syntax is much cleaner.
The ReactNode Type

When writing TypeScript, type children as ReactNode — the broadest type that covers everything React can render:

TSX
import { ReactNode } from 'react'

interface CardProps {
  children: ReactNode
  title?: string
}

function Card({ children, title }: CardProps) {
  return (
    <div className="card">
      {title && <h3>{title}</h3>}
      <div className="card-body">{children}</div>
    </div>
  )
}
Tip
`ReactNode` covers JSX elements, strings, numbers, arrays of elements, fragments, `null`, and `undefined`. It is the right type for any prop that accepts "anything renderable".
Layout / Wrapper Components

The children pattern shines for layout components — components whose job is to provide structure, spacing, or a visual shell around whatever content gets passed in. The layout component does not need to know anything about the content:

JSX
// A page layout wrapper
function PageContainer({ children }) {
  return (
    <main style={{ maxWidth: 960, margin: '0 auto', padding: '0 16px' }}>
      {children}
    </main>
  )
}

// A two-column layout
function TwoColumnLayout({ sidebar, children }) {
  return (
    <div style={{ display: 'flex', gap: 24 }}>
      <aside style={{ width: 240, flexShrink: 0 }}>{sidebar}</aside>
      <section style={{ flex: 1 }}>{children}</section>
    </div>
  )
}

// Usage:
function Dashboard() {
  return (
    <PageContainer>
      <TwoColumnLayout sidebar={<Navigation />}>
        <h1>Welcome back</h1>
        <RecentActivity />
      </TwoColumnLayout>
    </PageContainer>
  )
}
A Modal Component Using children

JSX
function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null

  return (
    <div className="overlay" onClick={onClose}>
      <div
        className="modal"
        onClick={(e) => e.stopPropagation()}
      >
        <button className="close-btn" onClick={onClose}>
          ✕
        </button>
        {children}
      </div>
    </div>
  )
}

// The Modal does not care what content goes inside it:
function App() {
  const [open, setOpen] = useState(false)

  return (
    <>
      <button onClick={() => setOpen(true)}>Open Modal</button>

      <Modal isOpen={open} onClose={() => setOpen(false)}>
        <h2>Confirm Action</h2>
        <p>Are you sure you want to delete this item?</p>
        <button onClick={() => setOpen(false)}>Cancel</button>
        <button onClick={handleDelete}>Delete</button>
      </Modal>
    </>
  )
}
Multiple Children and Text

children can be a single element, multiple elements, plain text, a number, or even null. React handles all of these:

JSX
// Single element child
<Card><p>One paragraph</p></Card>

// Multiple children — React stores them as an array
<Card>
  <h2>Title</h2>
  <p>Paragraph one</p>
  <p>Paragraph two</p>
</Card>

// Plain text child
<Badge>New</Badge>

// Number child
<Badge>{42}</Badge>

// No children — children is undefined
<Card />
The React.Children API

When you need to inspect or transform children programmatically, React provides the React.Children utility. The most useful methods are React.Children.count(), React.Children.map(), and React.Children.toArray():

JSX
import React from 'react'

// A Tabs component that wraps each child in a tab panel:
function Tabs({ children }) {
  const [active, setActive] = useState(0)
  const childArray = React.Children.toArray(children)

  return (
    <div>
      {/* Tab buttons */}
      <div role="tablist">
        {childArray.map((child, i) => (
          <button
            key={i}
            role="tab"
            aria-selected={i === active}
            onClick={() => setActive(i)}
          >
            {child.props.label}
          </button>
        ))}
      </div>

      {/* Active panel */}
      <div role="tabpanel">{childArray[active]}</div>
    </div>
  )
}

// A Tab component that is just a data holder:
function Tab({ label, children }) {
  return <div>{children}</div>
}

// Usage:
<Tabs>
  <Tab label="Overview">...</Tab>
  <Tab label="Details">...</Tab>
  <Tab label="Reviews">...</Tab>
</Tabs>
Why the children Pattern Is Powerful
  • Inversion of control — the parent decides what content goes inside; the wrapper component just provides the shell

  • DecouplingCard, Modal, and PageContainer work with any content without importing or knowing about it

  • Composition over configuration — instead of passing a dozen props describing what to render, you just nest the elements you want

  • Reuse without inheritance — layout patterns compose freely without class inheritance

  • TypeScript-friendlyReactNode accurately represents everything React can render

Tip
The children pattern is the primary way React achieves the "composition over inheritance" principle. Whenever you find yourself passing components as props (e.g. `content={<MyContent />}`) consider whether the children slot is a cleaner fit.