apps/mantine.dev/src/pages/guides/polymorphic.mdx
import { GuidesDemos } from '@docs/demos'; import { Layout } from '@/layout'; import { MDX_DATA } from '@/mdx';
export default Layout(MDX_DATA.Polymorphic);
A polymorphic component is a component whose root element can be changed with the component prop.
All polymorphic components have a default element that's used when the component prop is not provided.
For example, the Button component's default element is button and
it can be changed to a or any other element or component:
renderRoot is an alternative to the component prop, which accepts a function that should return
a React element. It is useful in cases when component prop cannot be used, for example,
when the component that you want to pass to the component is generic
(accepts type or infers it from props, for example <Link<'/'> />).
Example of using renderRoot prop, the result is the same as in the previous demo:
import { Button } from '@mantine/core';
function Demo() {
return (
<Button
renderRoot={(props) => (
<a href="https://mantine.dev/" target="_blank" {...props} />
)}
>
Mantine website
</Button>
);
}
!important It's required to spread the props argument into the root element. Otherwise,
there will be no styles and the component might not be accessible.
You can pass any other React component to the component prop.
For example, you can pass the Link component from react-router-dom:
import { Link } from 'react-router-dom';
import { Button } from '@mantine/core';
function Demo() {
return (
<Button component={Link} to="/react-router">
React router link
</Button>
);
}
The Next.js link doesn't work in the same way as other similar components in all Next.js versions.
With Next.js 12 and below:
import Link from 'next/link';
import { Button } from '@mantine/core';
function Demo() {
return (
<Link href="/hello" passHref>
<Button component="a">Next link button</Button>
</Link>
);
}
With Next.js 13 and above:
import Link from 'next/link';
import { Button } from '@mantine/core';
function Demo() {
return (
<Button component={Link} href="/hello">
Next link button
</Button>
);
}
You cannot pass generic components to the component prop because it's not possible to infer generic types
from the component prop. For example, you cannot pass typed Next.js Link
to the component prop because it's not possible to infer the href type from the component prop. The component itself
will work correctly, but you'll have a TypeScript error.
To make generic components work with polymorphic components, use the renderRoot prop instead of component:
import Link from 'next/link';
import { Button } from '@mantine/core';
function Demo() {
return (
<Button renderRoot={(props) => <Link href="/hello" {...props} />}>
Typed Next link button
</Button>
);
}
The react-router-dom NavLink component's
className prop accepts a function based on which you can add an active class to the link. This feature is
incompatible with Mantine's component prop, but you can use the renderRoot prop instead:
import cx from 'clsx';
import { NavLink } from 'react-router-dom';
import { Button } from '@mantine/core';
function Demo() {
return (
<Button
renderRoot={({ className, ...others }) => (
<NavLink
className={({ isActive }) =>
cx(className, { 'active-class': isActive })
}
{...others}
/>
)}
>
React router NavLink
</Button>
);
}
Non-polymorphic components include React.ComponentProps<'x'> as part of their props type,
where x is the root element of the component. For example, the Container component
is not polymorphic – its root element is always div, so its props type includes React.ComponentProps<'div'>.
Polymorphic components don't include React.ComponentProps<'x'> as part of their props type
because their root element can be changed, and thus the props type can be inferred only after the component was rendered.
Example of creating a non-polymorphic wrapper component for Mantine polymorphic component:
<Demo data={GuidesDemos.staticPolymorphic} />Example of creating a polymorphic wrapper component for Mantine polymorphic component:
<Demo data={GuidesDemos.createPolymorphic} />You can use a dynamic value in the component prop, but in this case, you need to either provide types manually
or disable type checking by passing any as a type argument to the polymorphic component:
import { Box } from '@mantine/core';
function KeepTypes() {
return (
<Box<'input'>
component={(Math.random() > 0.5 ? 'input' : 'div') as any}
/>
);
}
function NukeTypes() {
return (
<Box<any> component={Math.random() > 0.5 ? 'input' : 'div'} />
);
}
Use the polymorphic function and Box component to create new polymorphic components:
Polymorphic components have a performance overhead for tsserver (no impact on runtime performance),
because of that, not all Mantine components have polymorphic types, but all components still
accept the component prop – the root element can be changed.
To make a Mantine component polymorphic, use the polymorphic function the same way
as in the previous example:
import { polymorphic, Group, GroupProps } from '@mantine/core';
const PolymorphicGroup = polymorphic<'button', GroupProps>(Group);
function Demo() {
return (
<PolymorphicGroup component="a" href="https://mantine.dev" />
);
}