Tutorial
Slot Pattern

The Slot Pattern

The slot pattern enables you to break down parent-provided content into multiple parts and instruct a component on where to render each part by placing slots.

In a typical React component, this is accomplished using 'render props.' Components receive different parts of content as props and render each one in their specified slots. For instance, a typical Button component might accept children, leftIcon, and rightIcon props, rendering them in their predetermined places if provided.

With react-slots, we don't use props to define slot content; everything is managed through children.

Writing Slotted Components

To begin placing slots, you need to use the useSlot hook. useSlot takes in the children and returns a slot object for this component. slot is a dynamic object, where any key gives you access to corresponding parent-provided content. Values on the slot object are just like any other React element. You can place these elements wherever you want inside the component.

Let's implement the Button component using react-slots:

import { useSlot } from "@beqa/react-slots";
 
function Button({ children, onClick }) {
  const { slot } = useSlot(children);
 
  return (
    <button onClick={onClick}>
      <span className="left">
        <slot.leftIcon />
      </span>
      <slot.default />
      <span className="right">
        <slot.rightIcon />
      </span>
    </button>
  );
}

Here, you can see that we divided the children into three parts: leftIcon, rightIcon, and default. This means the parent now has to somehow specify contents for each of these to replace the slots inside the component.

Using Slotted Components

When passing children to this component, you need a way to designate which elements are for the leftIcon, rightIcon, and default slots. You achieve this by adding a slot-name attribute to any element or component passed to the Button as children (note: elements marked with slot-name must be direct children of the Button). The default slot is special because any top-level node lacking a slot-name attribute automatically lands in the default slot.

Let's create an "[ Add + ]" button using this component:

import Button from "./Button.jsx";
 
function SomeComponent() {
  return (
    <div>
      Add item to My collection?
      <Button>
        Add
        <span slot-name="rightIcon" className="my-plus-icon">
          +
        </span>
      </Button>
    </div>
  );
}

The rendered HTML from this component will look like this:

<div>
  Add item to My collection?
  <button>
    <span class="left" />
    Add
    <span class="right">
      <span class="my-plus-icon"> + </span>
    </span>
  </button>
</div>

As you can see, some interesting things have happened here:

  • The slot for leftIcon didn't render anything because it wasn't specified by the parent.
  • The slot for rightIcon rendered the parent's span and removed the slot-name attribute from the final HTML output.
  • The default slot rendered a string "Add" implicitly because it did not have the "slot-name" attribute.

There are two more ways to specify slot content from the parent which are explored in detail in their dedicated sections: Templates, Type-safe templates

Why not just use props?

  • Slots offer a convenient syntactic sugar for providing fallbacks and passing the props up.
  • react-slots draws inspiration from Web components (opens in a new tab), which is HTML's proposed standard for creating HTML-like elements. With react-slots, you can design components that function similarly to native HTML elements.
  • By enforcing the restriction that children is for specifying content and props are for modifying content, react-slots allows you to grant parents the freedom to specify free-form content and own their own markup. Simultaneously, it enables you to enforce strict relationships for composed elements with OverrideNode.