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
ref
prop and merge it with the internal ref, if any. - 3
Merge the
style
andclassName
props 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: