DEV Community

Cover image for React Children vs Slots: When to Use Each Pattern?
Aleksandra Dudkina
Aleksandra Dudkina

Posted on Originally published at aleksandradudkina.hashnode.dev

React Children vs Slots: When to Use Each Pattern?

For a long time, when I needed to make a React component flexible, my first thought always was:

Just pass everything through children.

And in many cases, that's exactly what you should do.

But once a component has several places where custom content can go, children can become a little too generic.

That's where the slot pattern becomes useful.

The confusing part is that React doesn't actually have a built-in Slot concept like some other frameworks do.

So what do people mean when they talk about slots in React?

Let's compare.

children: one place for any content

The most basic composition pattern in React is children.

type CardProps = {
  children: React.ReactNode;
};

function Card({ children }: CardProps) {
  return <div className="card">{children}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Then we can put anything inside:

<Card>
  <h2>Profile</h2>
  <p>Some information about the user</p>
</Card>
Enter fullscreen mode Exit fullscreen mode

React passes everything between <Card> and </Card> through the children prop.

I usually think of children as:

Here is one area of the component. Put whatever you want inside it.

This works really well for things like:

  • cards

  • modals

  • layout wrappers

  • providers

  • containers

But what if the component has several separate customizable areas?

What if I need more than one children?

Imagine a modal:

<Modal>
  ...
</Modal>
Enter fullscreen mode Exit fullscreen mode

It might have:

  • a title

  • main content

  • a footer

  • actions

Technically, we could put everything into children:

<Modal>
  <h2>Delete account?</h2>

  <p>This action cannot be undone.</p>

  <div>
    <Button>Cancel</Button>
    <Button>Delete</Button>
  </div>
</Modal>
Enter fullscreen mode Exit fullscreen mode

This is fine if Modal doesn't need to know anything about that structure.

But sometimes it does.

Maybe the modal needs to render the title in one container, the content in another and the actions inside a sticky footer.

Then a single children prop is not very convenient.

Slots are basically named places for content

One common React version of the slot pattern is simply passing React nodes through different props.

type ModalProps = {
  title: React.ReactNode;
  children: React.ReactNode;
  footer?: React.ReactNode;
};

function Modal({ title, children, footer }: ModalProps) {
  return (
    <div className="modal">
      <header>{title}</header>

      <main>{children}</main>

      {footer && <footer>{footer}</footer>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now usage looks like this:

<Modal
  title="Delete account?"
  footer={
    <>
      <Button>Cancel</Button>
      <Button>Delete</Button>
    </>
  }
>
  <p>This action cannot be undone.</p>
</Modal>
Enter fullscreen mode Exit fullscreen mode

Here:

  • children is the main content

  • title is one slot

  • footer is another slot

So, at least conceptually:

children is an unnamed/default slot, while slots give different pieces of content explicit destinations.

There is no special React syntax involved.

They're just props containing React nodes.

Why not use props for everything?

We could go even further:

<Modal
  title="Delete account?"
  description="This action cannot be undone."
  cancelText="Cancel"
  confirmText="Delete"
/>
Enter fullscreen mode Exit fullscreen mode

And sometimes that's better.

The difference is that ordinary data props describe what the component should render, while slots usually let the consumer provide the actual UI.

For example:

footer={<CustomActions />}
Enter fullscreen mode Exit fullscreen mode

gives much more freedom than:

confirmText="Delete"
Enter fullscreen mode Exit fullscreen mode

Neither approach is always better.

It depends on how much control the component should expose.

Slots can make the API clearer

Let's imagine a component with several visual areas.

With generic children, I might need to invent additional wrappers:

<Card>
  <CardHeader>
    <h2>Settings</h2>
  </CardHeader>

  <CardContent>
    ...
  </CardContent>

  <CardFooter>
    <Button>Save</Button>
  </CardFooter>
</Card>
Enter fullscreen mode Exit fullscreen mode

This is the compound component pattern, which is another common way to implement slot-like APIs.

Alternatively, I could make the slots explicit:

<Card
  header={<h2>Settings</h2>}
  footer={<Button>Save</Button>}
>
  ...
</Card>
Enter fullscreen mode Exit fullscreen mode

Both APIs represent roughly the same idea:

This component has several predefined places where custom content can be inserted.

Which one I choose usually depends on the complexity of the component.

For something small, named props are often simpler.

For a larger component with many related parts, compound components can feel more natural.

A slot doesn't have to contain JSX

Another thing that confused me at first was thinking that every customizable area should be a ReactNode.

Sometimes a render function is more useful.

For example:

<List
  items={users}
  renderItem={(user) => (
    <UserCard user={user} />
  )}
/>
Enter fullscreen mode Exit fullscreen mode

renderItem is not technically the same API as:

item={<UserCard />}
Enter fullscreen mode Exit fullscreen mode

because the component passes data back to us.

But conceptually, it's solving a similar composition problem:

The library controls where something is rendered, while I control what gets rendered there.

You'll see this pattern a lot in component libraries.

So when do I use children?

I usually prefer children when the component has one obvious content area.

For example:

<Popover>
  <SettingsForm />
</Popover>
Enter fullscreen mode Exit fullscreen mode

or:

<PageLayout>
  <Dashboard />
</PageLayout>
Enter fullscreen mode Exit fullscreen mode

It's simple, familiar and doesn't add unnecessary API.

When do slots make more sense?

Slots become more useful when a component has several independent customizable areas.

For example:

<Page
  header={<Header />}
  sidebar={<Navigation />}
  footer={<Footer />}
>
  <Content />
</Page>
Enter fullscreen mode Exit fullscreen mode

In this case, trying to put everything into a single children prop would probably make the API less explicit.

Slots also make sense when the component needs to control where each piece is rendered.

children vs slots

children Slots
One default content area Multiple named content areas
Built into React A composition pattern
Passed through props.children Usually implemented with props or compound components
Great for wrappers and containers Great for structured reusable components
Parent mostly provides content Component controls where each piece goes

And the important part:

They're not really competing concepts.

children can actually be one of the slots.

For example:

function Modal({
  title,
  children,
  footer,
}: ModalProps) {
  return (
    <>
      <header>{title}</header>
      <main>{children}</main>
      <footer>{footer}</footer>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here children is simply the default slot.

Final takeaway

React gives us children, but "slots" are mostly a pattern we build on top of React's normal composition model.

If my component has one customizable area, I usually reach for children.

If it has several distinct areas, I start thinking about named slots:

header
sidebar
footer
actions
Enter fullscreen mode Exit fullscreen mode

or compound components:

<Card.Header />
<Card.Content />
<Card.Footer />
Enter fullscreen mode Exit fullscreen mode

The goal isn't to use the more sophisticated pattern.

It's to make the component API obvious.

If I have to open the component implementation just to understand where my children will end up, that's usually a sign that the API could be clearer.

Top comments (0)