Volume I index
Components Before Abstractions

Children as Composition

Ishtmeet Singh @ishtms/July 14, 2026/7 min read
#react#children#composition#components#props

Props can carry data such as a title or lesson count. They can also carry React content. JSX nested between a component's opening and closing tags is supplied through the prop named children.

jsx
<Panel>
  <p>Read the lesson before continuing.</p>
</Panel>

Panel receives the paragraph as props.children. The parent chooses the nested content. Panel chooses where that content appears inside its own structure.

This division is composition. Components are assembled through their public inputs without one component importing every possible child that can appear inside it.

Receive the children Prop

Define a wrapper.

jsx
function Panel({ children }) {
  return (
    <section className="panel">
      {children}
    </section>
  );
}

Use it with nested content.

jsx
<Panel>
  <p>Read the lesson before continuing.</p>
</Panel>

The component element conceptually receives one prop.

text
children -> paragraph element

When Panel returns {children} inside the section, React continues through that supplied paragraph element. React DOM creates the section and paragraph in the resulting host tree.

The wrapper does not call the paragraph as a component and does not convert it to HTML. It receives a React node value through its props object.

Nested Text Also Becomes Children

Plain nested text is supplied through children.

jsx
<Panel>Read the lesson before continuing.</Panel>

Panel receives a string. React can render that string inside the returned section.

jsx
function Panel({ children }) {
  return <section>{children}</section>;
}

Do not assume children is always an element object. Depending on the caller, it can be text, a number, an element, several nodes, or an empty value.

TypeScript represents a prop that accepts the normal range of renderable React children with ReactNode.

Several Nested Nodes Form One Children Value

A caller can supply several sibling nodes.

jsx
<Panel>
  <h2>Before you continue</h2>
  <p>Save the current file.</p>
</Panel>

React represents the children in the element's props. The wrapper can render them together with {children}.

Do not depend on children always being a normal array. A single child is not wrapped into an array merely for consistency, and React can use other internal representations. Treat children as opaque React content unless a component has a defined reason to transform it.

React supplies Children helpers for selected legacy and library patterns. They are not the default method for ordinary composition. Explicit data arrays are clearer when a component needs to map records, filter items, or attach IDs.

A Wrapper Adds Its Own Structure

Panel can add a heading while leaving the body open to the parent.

jsx
function Panel({ title, children }) {
  return (
    <section className="panel">
      <h2>{title}</h2>
      <div className="panel-body">{children}</div>
    </section>
  );
}

The parent supplies both a data prop and nested content.

jsx
<Panel title="Chapter progress">
  <LessonProgress completed={2} total={6} />
</Panel>

Panel defines the outer section, heading, and body container. The parent supplies LessonProgress as the body content.

This keeps the wrapper reusable without adding a prop for every content combination.

Compare Fixed Content with Open Content

This component fixes its body.

jsx
function ProgressPanel() {
  return (
    <section>
      <h2>Progress</h2>
      <LessonProgress />
    </section>
  );
}

It is appropriate when the component's responsibility is specifically the progress region.

This component accepts open content.

jsx
function Panel({ title, children }) {
  return (
    <section>
      <h2>{title}</h2>
      {children}
    </section>
  );
}

It is appropriate when several page sections share the same container structure.

Neither version is universally better. Use fixed content when the feature is part of every instance. Use children when callers should choose what appears inside the wrapper.

Avoid a Prop for Every Markup Detail

A card API can become tied to one layout.

jsx
<Card
  heading="Props as Inputs"
  paragraph="Fourteen minutes"
  buttonText="Open"
/>

The component must define the exact arrangement and behavior for every field. Adding a second paragraph or a list requires more props and more internal branches.

Composition can keep the card body open.

jsx
<Card>
  <h2>Props as Inputs</h2>
  <p>Fourteen minutes</p>
  <button type="button">Open</button>
</Card>

The wrapper can remain small.

jsx
function Card({ children }) {
  return <article className="card">{children}</article>;
}

Do not replace every descriptive prop with children. A prop named status communicates a supported component behavior better than asking each caller to rebuild status markup. Children fit content slots. Named props fit data and behavior contracts.

Children Can Contain Components

The nested content can include host elements and custom components.

jsx
<Panel title="Lessons">
  <LessonList />
  <ContinueButton />
</Panel>

Panel does not need imports for LessonList or ContinueButton. The parent module creates those elements and passes them in.

This lowers dependency coupling. The generic wrapper depends only on React content; the page chooses the feature components placed inside it.

The React component tree retains all component occurrences.

text
ChapterPage
  Panel
    LessonList
    ContinueButton

The DOM tree follows the host elements returned by each component.

Children Can Be Passed Explicitly

Because children is a prop, this form is valid.

jsx
<Panel children={<p>Saved</p>} />

The nested form is clearer for normal composition.

jsx
<Panel>
  <p>Saved</p>
</Panel>

Do not supply both forms on one element. They compete for the same prop name, and the source no longer communicates which content should win.

Explicit children can be useful when props are assembled programmatically, but it should remain uncommon in handwritten component trees.

Missing Children Produce an Empty Position

Render Panel without nested content.

jsx
<Panel title="Chapter progress" />

children is undefined. React renders no host content for the expression.

The section and heading still appear because Panel returns them.

jsx
function Panel({ title, children }) {
  return (
    <section>
      <h2>{title}</h2>
      {children}
    </section>
  );
}

If a panel requires body content, make that requirement part of its public API and supply an appropriate empty result at runtime. Do not insert placeholder text at the wrapper level unless every use should display it.

Use Named Slots for Several Regions

One children position is sufficient for many wrappers. A layout with several caller-controlled regions can use named React node props.

jsx
function LessonLayout({ header, sidebar, children }) {
  return (
    <div className="lesson-layout">
      <header>{header}</header>
      <aside>{sidebar}</aside>
      <main>{children}</main>
    </div>
  );
}

The caller supplies each region.

jsx
<LessonLayout
  header={<ChapterHeading />}
  sidebar={<LessonNav />}
>
  <LessonArticle />
</LessonLayout>

header and sidebar are ordinary props holding React nodes. children fills the primary open region.

Use this pattern only when the layout genuinely exposes several caller-controlled regions. A long list of slots can make a component harder to use than direct page markup.

Do Not Mutate or Reparent Child Elements

React element values are immutable descriptions. A wrapper should render the supplied children rather than change their props object.

jsx
function Panel({ children }) {
  children.props.className = "inside-panel";
  return <section>{children}</section>;
}

This mutation is unsupported and also fails when children is text, several nodes, or empty.

Put layout classes on the wrapper.

jsx
function Panel({ children }) {
  return <section className="panel-content">{children}</section>;
}

If a child needs a behavior prop, create that child with the prop at the owning parent.

jsx
<Panel>
  <LessonList compact />
</Panel>

This keeps the contract visible where the element is created.

Build the Chapter Page Through Composition

Create a reusable Panel.

jsx
function Panel({ title, children }) {
  return (
    <section className="panel">
      <h2>{title}</h2>
      {children}
    </section>
  );
}

Compose the page.

jsx
<Panel title="Chapter progress">
  <LessonProgress completed={2} total={6} />
</Panel>

Use the same structure for another content type.

jsx
<Panel title="Continue reading">
  <LessonCard title="Conditional UI" minutes={16} />
</Panel>

The wrapper stays unchanged. The page retains control of the actual body content.

Lesson Check

The composition path can now be stated precisely.

text
the parent creates nested React nodes
JSX stores them in the children prop
React calls the wrapper with that prop
the wrapper places children in its returned tree
React continues rendering the supplied nodes there

Source Notes