Skip to main content

Slot

Merges its props onto its immediate child. This is a utility component used internally by other components to enable the asChild pattern.

Visit ngrok
import { Slot } from "@ngrok/mantle/slot"; <Slot className="rounded bg-blue-500 px-4 py-2 text-white">	<a href="https://ngrok.com">Visit ngrok</a></Slot> <Slot className="rounded bg-red-500 px-4 py-2 text-white">	<button type="button">Click me</button></Slot>

Usage with asChild

The Slot component is the foundation of the asChild pattern used throughout Mantle. When a component supports asChild, it uses Slot internally to merge its props onto the child element instead of rendering a wrapper.

import { Button } from "@ngrok/mantle/button"; <Button appearance="filled" intent="neutral" asChild>	<a href="https://ngrok.com">Visit ngrok</a></Button> <Button appearance="outlined" intent="neutral" asChild>	<a href="https://github.com/ngrok/ngrok">View on GitHub</a></Button>

Class name merging

Slot automatically merges className props using Tailwind CSS's merge logic. Child classes take priority over parent classes for conflicting styles, while non-conflicting classes are preserved.

Child bg-red-500 wins, parent px-4 and py-2 merge
import { Slot } from "@ngrok/mantle/slot"; <Slot className="rounded bg-blue-500 px-4 text-white">	<div className="bg-red-500 py-2">Child bg-red-500 wins, parent px-4 and py-2 merge</div></Slot>; // Result: className="bg-red-500 rounded px-4 py-2 text-white"

Render props on the child

Some components resolve their own className or style from state only they know: react-router's NavLink calls className={({ isActive }) => …} with its own match state. Slot composes those: when the child's className or style is a function, the merged value is a function of the same shape, which forwards the child's own arguments to the child's function and merges the result at the same child-wins precedence.

That is what lets a router link keep its active styling through a mantle part's asChild:

import { Sidebar } from "@ngrok/mantle/sidebar";import { GraphIcon } from "@phosphor-icons/react/Graph";import { NavLink } from "react-router"; <Sidebar.Item>	<Sidebar.ItemButton asChild>		<NavLink className={({ isActive }) => (isActive ? "font-medium" : undefined)} to="/endpoints">			<GraphIcon />			Endpoints		</NavLink>	</Sidebar.ItemButton></Sidebar.Item>;

Slot never calls the function itself: it cannot know the argument is { isActive, isPending, isTransitioning }, and it does not need to. NavLink calls it.

Reach for this to add styling of your own. You do not need it for the active row treatment a mantle part already ships: Sidebar.ItemButton styles its current row from aria-current="page" as well as data-current, and NavLink sets that attribute itself, so <Sidebar.ItemButton asChild><NavLink to="…" /></Sidebar.ItemButton> highlights with no className, no current, and no useMatch.

A function children is forwarded untouched, so NavLink's children={({ isActive }) => …} works too.

note

This is Slot's behavior, so it covers every part that composes through it. A part that forwards asChild straight to a Radix primitive (Tooltip.Trigger, DropdownMenu.Trigger, Dialog.Trigger) uses Radix's own slot, which joins a function className into the class string instead of composing it. Put the render prop on a mantle-backed part: nesting works, so DropdownMenu.Trigger asChild > Sidebar.ItemButton asChild > NavLink composes fine.

API Reference

Slot

Merges its props onto its immediate child element. Automatically handles className merging with Tailwind CSS merge logic.

Based on Radix UI Slot.

PropTypeDefaultDescription
childrenReactNodeA single child element. The Slot will merge its props onto this child.
className?stringCSS classes to merge with the child's className. Uses Tailwind CSS merge logic where child classes win for conflicting properties. A function className on the child is composed, not dropped.
style?CSSPropertiesInline styles to merge with the child's, per property, where the child wins. A function style on the child is composed, not dropped.