Slot
Component composition mechanism that merges props onto a single child element.
Slot
The Slot component is the core mechanism behind the asChild pattern. It takes its props and merges them onto a single React child element, composing handlers, merging classes, and forwarding refs. This allows components to avoid wrapper elements while maintaining full prop flexibility.
You rarely use Slot directly. Instead, you use asChild on Primitive and other components, which internally uses Slot.
Features
- Merges props onto a single child element
- Composes event handlers (both functions called)
- Merges class names (both applied)
- Merges styles (child styles take precedence)
- Forwards refs correctly
- Works with React fragments and lazy components
Usage
import { Slot } from '@loke/ui/slot';
// Direct Slot usage (rare)
<Slot className="text-blue-600" onClick={onSlotClick}>
<button onClick={onButtonClick}>Click me</button>
</Slot>
// Result: button has both class and both click handlers called
// More common: via Primitive's asChild
import { Primitive } from '@loke/ui/primitive';
<Primitive.button asChild className="p-2 bg-blue-600">
<a href="/page">Link styled as button</a>
</Primitive.button>Props
Prop
Type
How Props Are Merged
When Slot merges props:
- Event handlers: Both called in sequence (Slot handler first, then child handler)
- className: Both combined with space separator
- style: Child style takes precedence (merged objects)
- ref: Composed onto child ref
- Other props: Child props override Slot props
Examples
- Click the button to see both handlers fire
Slot merges its className onto this span
Composing handlers
<Slot onClick={() => console.log('slot clicked')} className="cursor-pointer">
<button onClick={() => console.log('button clicked')}>
Click me
</button>
</Slot>
// Both console.log statements run when clickedStyling a link as a button
import { Button, buttonVariants } from '@loke/design-system/button';
import { Slot } from '@loke/ui/slot';
// This is effectively what asChild does:
<Slot className={buttonVariants({ variant: 'primary' })}>
<a href="/next-page">Go to next page</a>
</Slot>Merging classes
<Slot className="text-blue-600 p-4">
<div className="bg-white rounded">Content</div>
</Slot>
// Result: <div> has all three classes appliedTechnical Details
Slot handles special React cases:
- React.Fragment: No Slot wrapper (props not applied to fragments)
- Lazy components: Waits for resolution before merging
- React 18 & 19 compatibility: Handles ref access differences between versions
Accessibility
Slot itself has no accessibility impact; it's purely structural. Ensure the child element:
- Has proper semantic HTML (button for buttons, a for links, etc.)
- Includes all necessary ARIA attributes
- Maintains keyboard interaction patterns
Best practices
- Use asChild on components instead of Slot directly
- Only one child per Slot; multiple children throw an error
- Remember that child props/handlers take precedence over Slot props in conflicts
- Test merged event handlers to ensure both functions are called
- Be aware that class merging is simple (space-separated); for Tailwind conflicts, use utilities that override (e.g.,
bg-red-600!)