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

PropTypeDefault
render

RenderProp<State>

undefined

props

Record<string, unknown>

undefined

refs

React.Ref<RenderedElementType>[]

undefined

state

State

undefined

Return value

PropType
renderElement

() => React.ReactElement

renderElement function
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 tag

The 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:

  1. Event handlers, so that all are invoked
  2. className strings
  3. style 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.

Using mergeProps in the render callback
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:

React 19
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.

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 the render prop and HTML attributes.
  • useRender.ElementProps for the element’s internal (private) props. It types HTML attributes alone.
Typing props
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.

Radix UI Slot component
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.

Base UI render prop
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>} />