Composition
A guide to composing Base UI components with your own React components and elements.
A Flexible Foundation
In Base UI, composition is powered by a single, flexible concept: the render prop. This approach provides a clear and explicit way to customize the underlying structure of a component without sacrificing accessibility or functionality.
Think of it as a designated slot where you can plug in your own custom element or React component, giving you complete control over the final rendered output.
Changing the Default Element
At its core, the render prop lets you replace the default HTML element of a component with one of your own.
Every Base UI component renders the most appropriate element by default (e.g., a <button> for a trigger, a <div> for a menu item). However, you might need a Menu.Item to act as a link. The render prop makes this trivial.
import { Menu } from '@/lib/components/menu';
import { Button } from '@/lib/components/button';
export default function MyNavigation() {
return (
<Menu.Root>
<Menu.Trigger render={<Button variant="outline" />}>
Navigate
</Menu.Trigger>
<Menu.Portal>
<Menu.Positioner>
<Menu.Popup>
{/* The Menu.Item now renders as an <a> tag */}
<Menu.Item render={<a href="/home" />}>
Home
</Menu.Item>
<Menu.Item render={<a href="/settings" />}>
Settings
</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>
);
}Coming from Radix UI? asChild vs. render
If you're used to Radix UI, you're likely familiar with the asChild prop. The render prop in Base UI serves the same fundamental purpose — composing components — but with a slightly different and more explicit mental model.
Let's compare how you'd make a Button component behave like a link.
Radix UI (asChild)
With asChild, the Button's props are merged onto its direct descendant.
// The <Button>'s props are passed down to the <a> tag.
<Button asChild>
<a href="/profile">View Profile</a>
</Button>Base UI (render)
With render, you provide the element that the Button should render as. The children are passed through as normal.
// The <Button> renders an <a> tag instead of a <button>.
<Button render={<a href="/profile" />}>
View Profile
</Button>The render prop is intentionally explicit. It makes the component's structure clear at a glance and avoids the ambiguity that can sometimes arise from implicit prop merging.
Advanced Composition
The render prop's flexibility extends beyond just changing HTML tags.
Composing with Custom Components
The most powerful use case for render is composing Base UI primitives with your own custom React components. For example, you can use a custom, styled NextLink component as a Menu.Trigger.
import { Link } from '@/lib/components/next-link';
import { Menu } from '@/lib/components/menu';
<Menu.Trigger render={<NextLink href="/docs" />}>
Documentation
</Menu.Trigger>Using a Render Function for State Control
For maximum control, you can pass a function to the render prop instead of a React element. This is ideal for performance-critical scenarios or when you need to render different content based on the component's internal state.
The function receives two arguments: props (the props to be spread) and state (an object containing the component's current state).
For example, you could render a different icon inside a Switch.Thumb based on its checked state:
import { Switch } from '@/lib/components/switch';
import { CheckIcon, CloseIcon } from '@/lib/icons';
<Switch.Thumb
render={(props, state) => (
<span {...props}>
{state.checked ? <CheckIcon /> : <CloseIcon />}
</span>
)}
/>This pattern gives you direct access to the component's machinery, allowing for powerful and dynamic compositions while staying true to Base UI's philosophy of keeping you close to the metal.