Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/zayne-labs/ui/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Slot component merges its props with its child element, enabling prop forwarding and composition patterns. It’s useful for creating flexible, composable components that don’t add extra DOM nodes.

Import

import { Slot } from "@zayne-labs/ui-react/common/slot";
// or
import { SlotRoot, SlotSlottable } from "@zayne-labs/ui-react/common/slot";

Component Parts

Slot.Root

Merges props into the child element

Slot.Slottable

Marks content as the target for slotting

Slot.Root

Props

children
React.ReactElement
required
A single React element to merge props into. Must be a valid React element.
ref
React.Ref<HTMLElement>
Ref to be composed with the child’s ref.
All other props are merged with the child element’s props.

Slot.Slottable

Props

children
React.ReactNode
required
The content to be slotted. Must be a single React element.

Usage Examples

Basic Prop Merging

function Button({ asChild, ...props }) {
  const Component = asChild ? Slot.Root : 'button';
  
  return (
    <Component className="btn" {...props}>
      {props.children}
    </Component>
  );
}

// Usage - renders an <a> with merged classes and onClick
<Button asChild onClick={handleClick}>
  <a href="/home" className="link">Go Home</a>
</Button>
// Result: <a href="/home" className="btn link" onClick={handleClick}>Go Home</a>

Ref Composition

function Input({ asChild, ...props }) {
  const internalRef = useRef();
  const Component = asChild ? Slot.Root : 'input';
  
  return <Component ref={internalRef} {...props} />;
}

function Form() {
  const externalRef = useRef();
  
  // Both refs will be set
  return (
    <Input asChild ref={externalRef}>
      <input type="text" />
    </Input>
  );
}

Event Handler Merging

function Clickable({ asChild, onClick, ...props }) {
  const handleInternalClick = (e) => {
    console.log('Internal click');
    onClick?.(e);
  };
  
  const Component = asChild ? Slot.Root : 'div';
  
  return <Component onClick={handleInternalClick} {...props} />;
}

// Both click handlers will fire
<Clickable asChild onClick={() => console.log('External click')}>
  <button>Click me</button>
</Clickable>

Using Slottable

function Card({ asChild, children }) {
  const Component = asChild ? Slot.Root : 'div';
  
  return (
    <Component className="card">
      <div className="card-header">Header</div>
      <Slot.Slottable>{children}</Slot.Slottable>
      <div className="card-footer">Footer</div>
    </Component>
  );
}

// The article element receives the card class
<Card asChild>
  <article>
    <h2>Title</h2>
    <p>Content</p>
  </article>
</Card>

// Result:
// <article className="card">
//   <div className="card-header">Header</div>
//   <h2>Title</h2>
//   <p>Content</p>
//   <div className="card-footer">Footer</div>
// </article>

Creating Polymorphic Components

function Text({ asChild, size = 'md', ...props }) {
  const Component = asChild ? Slot.Root : 'span';
  
  return (
    <Component
      className={`text-${size}`}
      {...props}
    />
  );
}

// Render as different elements
<Text>Default span</Text>
<Text asChild><p>Paragraph with text styles</p></Text>
<Text asChild><h1>Heading with text styles</h1></Text>

Complex Composition

function Dialog({ asChild, open, onOpenChange, children }) {
  const Component = asChild ? Slot.Root : 'div';
  
  return (
    <Component
      role="dialog"
      aria-hidden={!open}
      data-state={open ? 'open' : 'closed'}
    >
      {children}
    </Component>
  );
}

<Dialog asChild open={isOpen} onOpenChange={setIsOpen}>
  <aside className="sidebar">
    <DialogContent />
  </aside>
</Dialog>

Style Merging

function Box({ asChild, ...props }) {
  const Component = asChild ? Slot.Root : 'div';
  
  return (
    <Component
      style={{ padding: '1rem', border: '1px solid' }}
      {...props}
    />
  );
}

// Styles are merged
<Box asChild>
  <section style={{ backgroundColor: 'blue' }}>
    Content
  </section>
</Box>
// Result has both padding/border AND backgroundColor

With forwardRef

const CustomButton = forwardRef(({ asChild, ...props }, ref) => {
  const Component = asChild ? Slot.Root : 'button';
  
  return (
    <Component
      ref={ref}
      className="custom-btn"
      {...props}
    />
  );
});

function App() {
  const btnRef = useRef();
  
  return (
    <CustomButton asChild ref={btnRef}>
      <a href="/">Link Button</a>
    </CustomButton>
  );
}

Notes

  • The component only accepts a single child element (enforced via Children.only)
  • Props are merged using a custom mergeProps utility that handles event handlers and styles intelligently
  • Refs are composed so both parent and child refs work correctly
  • When the child is a Fragment, ref composition is skipped
  • Slot.Slottable helps preserve additional wrapper content while slotting the main child
  • Class names, styles, and event handlers are intelligently merged, not replaced
  • Based on Radix UI’s Slot implementation

Build docs developers (and LLMs) love