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
leftIcondidn't render anything because it wasn't specified by the parent. - The slot for
rightIconrendered the parent'sspanand removed theslot-nameattribute 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-slotsdraws 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
childrenis for specifying content and props are for modifying content,react-slotsallows 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.