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'sspan
and removed theslot-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. Withreact-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.