Composition
The render prop makes Ariakit components more versatile, allowing you to easily replace the default HTML element or enhance its features with custom components.
Changing the HTML element
The render prop lets you specify a different HTML element to be rendered instead of the default element.
For instance, the Textarea with inline Combobox example uses the render prop to replace the default input element rendered by the Combobox component with a textarea element. You can pass specific textarea props to the element directly:
The Combobox with links example renders the ComboboxItem component as an anchor element. Specific props should be passed to their corresponding components:
/>
The Dialog with details & summary example uses the render prop to replace the default button element of the Button component with a summary element:
Composing with custom components
Besides changing the underlying HTML element, the render prop can be used to render a custom component.
In the Menu with Framer Motion example, the render prop is used to render the Menu component as a motion.div element:
The Tab with Next.js App Router example uses the render prop to render the Tab component as the custom Link component from Next.js:
Merging the rendered element props
When passing an HTML element to the render prop, all the HTML props returned by the original component will be passed to the rendered element. The style, className, ref and event props will be automatically merged. In all other cases, the rendered element props will override the original component props.
-
Props coming from the original component will be passed to the rendered element:
<a id="item">...</a> -
Props passed directly to the rendered element will override the original component props:
<a id="link">...</a>
This also applies to the children prop. You don't need to nest children within the render prop. But if you do, the children passed to the rendered element will override the original component children.
-
Children coming from the original component will be passed to the rendered element:
<a>Ariakit</a> -
Children passed directly to the rendered element will override the original component children:
<a>Ariakit.org</a>
The same logic applies to custom components, but only for the props that are directly passed to the element within the render prop. The custom component itself is responsible for merging these props within its implementation. For more details, see Custom components must be open for extension.
Explicit render function
In addition to accepting a React element, the render prop can also receive a function that takes the original component's HTML props as a parameter and returns a React element. This provides you with greater control over the process of merging props:
This is useful when you want to compose the original component with custom components that accept HTML props through a custom prop:
Another use case for the render function is to render elements between an internal wrapper and the rendered element. This is how the Dialog with scrollable backdrop example renders a custom backdrop element that wraps the dialog, but is still inside the dialog portal:
Merging the render function props
When a function is passed to the render prop, the HTML props will not be automatically merged into the returned React element. It's your responsibility to merge them within the function itself, following the same approach described in Custom components must be open for extension.
With this in mind, it's generally recommended to pass all the HTML props you intend to merge to a transitional Role element passed directly to the render prop, as it can handle the merging process for you in a type-safe manner:
Custom components must be open for extension
When using the render prop with a custom component, you must ensure the component is open for extension. This means it should pass the incoming props, including event listeners and the forwarded ref prop, to the underlying element. Otherwise, the component may not work as expected.
This is a common pattern in most modern component libraries, so you shouldn't have problems with them. If you're using your own custom components, make sure they're open for extension by following these guidelines:
- 1
Spread all props onto the underlying element.
- 2
Forward the
refprop and merge it with the internal ref, if any. - 3
Merge the
styleandclassNameprops with the internal styles and classes, if any. - 4
Chain the event props with the internal event handlers, if any.
import { forwardRef, useRef } from "react";
import { mergeRefs } from "react-merge-refs";
const CustomButton = forwardRef(function CustomButton(props, forwardedRef) {
const internalRef = useRef(null);
return (
<button
{...props}
ref={mergeRefs([internalRef, forwardedRef])}
style={{
position: "relative",
...props.style,
}}
onClick={(event) => {
props.onClick?.(event);
// ...
}}
/>
);
});
Next steps
Continue reading our Guide to learn more about Ariakit: