useRender
Hook for enabling a render prop in custom components.
The useRender
hook lets you build custom components that provide a render
prop to override the default rendered element.
API reference
Input parameters
Prop | Type | Default | |
---|---|---|---|
render |
|
| |
props |
|
| |
refs |
|
| |
state |
|
|
Return value
Prop | Type | |
---|---|---|
renderElement |
|
const { renderElement } = useRender({
// Input parameters
});
const element = renderElement();
Examples
A render
prop for a custom Text component lets consumers use it to replace the default rendered p
element with a different tag or component.
Text component rendered as a paragraph tag
Text component rendered as a strong tagThe callback version of the render
prop enables more control of how props are spread, and also passes the internal state
of a component.
Merging props
The mergeProps
function merges two or more sets of React props together. It safely merges three types of props:
- Event handlers, so that all are invoked
className
stringsstyle
properties
mergeProps
merges objects from left to right, so that subsequent objects’ properties in the arguments overwrite previous ones. Merging props is useful when creating custom components, as well as inside the callback version of the render
prop for any Base UI component.
import { mergeProps } from '@base-ui-components/react/merge-props';
import styles from './index.module.css';
function Button() {
return (
<Component
render={(props, state) => (
<button
{...mergeProps<'button'>(props, {
className: styles.Button,
})}
/>
)}
/>
);
}
Merging refs
When building custom components, you often need to control a ref internally while still letting external consumers pass their own—merging refs lets both parties have access to the underlying DOM element. The refs
option in useRender
enables this, which holds an array of refs to be merged together.
In React 19, React.forwardRef()
is not needed when building primitive components, as the external ref prop is already contained inside props
. Your internal ref can be passed to refs
to be merged with props.ref
:
function Text({ render = <p />, ...props }: TextProps) {
const internalRef = React.useRef<HTMLElement | null>(null);
const { renderElement } = useRender({
render,
props,
refs: [internalRef],
});
return renderElement();
}
In older versions of React, you need to use React.forwardRef()
and add the forwarded ref to the refs
array along with your own internal ref.
The examples above assume React 19, and should be modified to use React.forwardRef()
to support React 18 and 17.
const Text = React.forwardRef(function Text(
{ render = <p />, ...props }: TextProps,
forwardedRef: React.ForwardedRef<HTMLElement>,
) {
const internalRef = React.useRef<HTMLElement | null>(null);
const { renderElement } = useRender({
render,
props,
refs: [forwardedRef, internalRef],
});
return renderElement();
});
TypeScript
To type props, there are two interfaces:
useRender.ComponentProps
for a component’s external (public) props. It types therender
prop and HTML attributes.useRender.ElementProps
for the element’s internal (private) props. It types HTML attributes alone.
interface ButtonProps extends useRender.ComponentProps<'button'> {}
function Button({ render = <button />, ...props }: ButtonProps) {
const defaultProps: useRender.ElementProps<'button'> = {
className: styles.Button,
type: 'button',
children: 'Click me',
};
const { renderElement } = useRender({
render,
props: mergeProps<'button'>(defaultProps, props),
});
return renderElement();
}
Migrating from Radix UI
Radix UI uses an asChild
prop, while Base UI uses a render
prop. Learn more about how composition works in Base UI in the composition guide.
In Radix UI, the Slot
component lets you implement an asChild
prop.
import { Slot } from 'radix-ui';
function Button({ asChild, ...props }) {
const Comp = asChild ? Slot.Root : 'button';
return <Comp {...props} />;
}
// Usage
<Button asChild>
<a href="/contact">Contact</a>
</Button>
In Base UI, useRender
lets you implement a render
prop. The example below is the equivalent implementation to the Radix example above.
import { useRender } from '@base-ui-components/react/use-render';
function Button({ render = <button />, ...props }) {
const { renderElement } = useRender({ render, props });
return renderElement();
}
// Usage
<Button render={<a href="/contact">Contact</a>} />