A polymorphic React component renders as whatever element the parent chooses through an as prop. Type it with a generic constrained to ElementType, so the element type flows into the props and ref automatically. The same component can be a button, a link, or a heading depending on the call site.
What a polymorphic component is
A normal Box always renders a div. A polymorphic Box lets the parent pick the tag:
function Box({ as = "div", children, ...rest }) {
const Tag = as;
return <Tag {...rest}>{children}</Tag>;
}The parent writes as="button" and the Box renders a button instead of a div. The challenge is typing as so the rest props match the chosen element. A well-typed as prop also fixes the props and the ref for the chosen tag, which is where TypeScript earns its keep.
Constrain the as prop
The as prop must be a valid element type. ElementType from React covers both tags and components, which makes it the right upper bound for the generic.
type BoxProps<C extends React.ElementType> = {
as?: C;
children: React.ReactNode;
} & React.ComponentPropsWithoutRef<C>;The generic C is constrained to ElementType. Intersecting with ComponentPropsWithoutRef of C means the accepted props depend on whichever element the caller picks. This intersection reuses the ComponentPropsWithoutRef utility for extracting element prop types.
The typed component
Put the pieces together and default the element to div:
function Box<C extends React.ElementType = "div">({
as,
children,
...rest
}: BoxProps<C>) {
const Tag = as || "div";
return <Tag {...rest}>{children}</Tag>;
}Now a Box with as="button" gets button props, and one with as="a" gets anchor props. TypeScript checks the attributes against the chosen element at each call site.
Use it in a real component
A design system Button often needs to act as a link without duplicating styles. With Box, the parent picks the tag:
function App() {
return (
<>
<Box as="button" type="submit">Save</Box>
<Box as="a" href="/docs">Docs</Box>
</>
);
}The first Box is a submit button and the second is a link. TypeScript checks each one against its chosen element, so a typo in an attribute fails at the call site. This keeps one styled component for many tags instead of a family of near-identical buttons.
When to reach for as
Use as when the same visual component must render as different semantic elements. A Button that can also act as a link is the classic case. The alternative is two components that share styles, which duplicates code.
Keep as optional and default it. Most callers get the common case, and only the exceptions pass a tag. Prefer two explicit components when the semantics genuinely differ, like a real link versus a button.
What to learn next
This pattern leans on generics. If the type parameter syntax is new, read the guide on typing generic components next.
Rune AI
Key Insights
- A polymorphic component uses an as prop to pick the tag.
- Constrain the generic with React.ElementType.
- Intersect props with ComponentPropsWithoutRef.
- Default the element to div when as is missing.
- TypeScript checks props against the chosen element.
Frequently Asked Questions
What is a polymorphic React component?
Why use ElementType to type the as prop?
How do I default the rendered element?
Conclusion
A polymorphic component renders as the element its parent chooses through an as prop. Type it with a generic constrained to ElementType, and intersect with ComponentPropsWithoutRef so props follow the chosen element.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.