Slot
A utility component that merges its props onto its immediate child.
Source Code
import { cn } from "@/lib/utils";
import { Children, cloneElement, isValidElement } from "react";
const isValidSlottableElement = (
value: unknown
): value is React.ReactElement<{
children?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
}> => {
return (
isValidElement(value) &&
!!value.props &&
typeof value.props === "object" &&
"key" in value
);
};
export const Slot = ({
children,
ref,
...props
}: React.ComponentPropsWithRef<React.ElementType>) => {
const element = Children.only(children);
if (isValidSlottableElement(element)) {
return cloneElement(element, {
...props,
...element.props,
ref,
style: {
...props.style,
...element.props.style,
},
className: cn(element.props.className, props.className),
});
}
throw new Error("Slot needs a valid react element child");
};
Slot.displayName = "Slot";
type SlottableProps = {
asChild: boolean;
child: React.ReactNode;
children: (child: React.ReactNode) => React.ReactElement;
};
/**
* Slottable is required when you want to use a Slot but render more than one child inside (e.g.: a button with the children + a spinner icon)
*
* see https://github.com/radix-ui/primitives/issues/1825
*/
export const Slottable = ({
asChild,
child,
children,
...props
}: SlottableProps) => {
return (
<>
{asChild
? isValidSlottableElement(child)
? cloneElement(child, props, children(child.props.children))
: null
: children(child)}
</>
);
};
Anatomy
<Slot>
<component />
</Slot>
Features
- Prop Merging: Automatically merges props from the Slot onto its child element
- Style Composition: Combines styles from both Slot and child element
- ClassName Merging: Intelligently merges classNames using the
cn
utility - Ref Forwarding: Properly forwards refs to the child element
- Type Safety: Full TypeScript support with proper type inference
API Reference
Slot
Prop | Default | Type | Description |
---|---|---|---|
| - |
| A single React element to merge props onto. |
| - |
| A ref to forward to the child element. |
Slottable
A utility component for handling multiple children in Slot-based components.
Prop | Default | Type | Description |
---|---|---|---|
| - |
| Whether to render the child as a Slot. |
| - |
| The child element to render. |
| - |
| Render function that receives the child and returns a React element. |
Examples
Basic Usage
Merging props onto a button element.
<Slot onClick={() => alert("clicked")} data-foo="bar">
<button>Click me</button>
</Slot>
/**
* Renders:
* <button data-foo="bar" onClick={() => alert("clicked")}>
* Click me
* </button>
*/
Using Slottable
Handling multiple children with the Slottable component.
<Slottable asChild child={<button>Click me</button>}>
{(child) => (
<>
<span>sufix</span>
{child}
<span>prefix</span>
</>
)}
</Slottable>
/**
* Renders:
* <button>
* <span>sufix</span>
* click me
* <span>prefix</span>
* </button>
*/
Common Use Cases
- Creating polymorphic components
- Building flexible UI components that can render as different elements
- Implementing component composition patterns
Technical Details
The Slot component uses React's cloneElement
to merge props onto its child element. It handles:
- Prop spreading
- Style composition
- ClassName merging
- Ref forwarding
- Type safety
The implementation ensures that props from the Slot take precedence over the child's props, while still preserving the child's original functionality.