Polymorphic React Components (the 'as' Prop) with Type Safety
How to type a polymorphic component like a design-system author: correct props, correct refs, and zero unsafe casts.
On this page
What you’ll learn
- The design-system pattern:
<Button as="a" href=...> - How to type props so they match the chosen element
- A pragmatic polymorphic typing approach that most teams can maintain
30‑second interview answer
A polymorphic component lets consumers change the underlying element via an as prop. The typing challenge is making props depend on as (e.g. href only when as="a"). The clean approach uses generics + ComponentPropsWithoutRef<T> to inherit the correct intrinsic props while adding your own.
The desired API
<Button onClick={() => {}}>Save</Button>
<Button as="a" href="/pricing">Pricing</Button>
// <Button as="a" onClick={...} /> // allowed
// <Button as="button" href="/x" /> // should be a type errorA pragmatic typing pattern
import type {
ElementType,
ComponentPropsWithoutRef,
ReactNode
} from "react";
type PolymorphicProps<T extends ElementType, OwnProps> =
OwnProps & {
as?: T;
} & Omit<ComponentPropsWithoutRef<T>, keyof OwnProps | "as">;
type ButtonOwnProps = {
variant?: "primary" | "secondary";
children?: ReactNode;
};
export function Button<T extends ElementType = "button">(
props: PolymorphicProps<T, ButtonOwnProps>
) {
const { as, variant = "primary", ...rest } = props;
const Comp = (as ?? "button") as ElementType;
return <Comp data-variant={variant} {...(rest as any)} />;
}Notes
- We inherit props from whatever
asis. - We omit collisions with our own props.
About the any cast:
React’s JSX typing sometimes forces a cast for spread props in generic components. In a production design system you can reduce/avoid this with forwardRef + more advanced typing, but the conceptual pattern is what matters.
Ref typing (advanced extension)
For a full design-system version, you typically add:
forwardRefComponentPropsWithRef<T>- a
PolymorphicRef<T>helper
That’s great content for a separate “29b” lesson if you want ultra-depth.
Common mistakes
React.FCfor polymorphic components (usually makes things worse)- Manually unioning props (
button | a | div | ...)—doesn’t scale - Losing
href/typecorrectness by typing everything asHTMLAttributes<HTMLElement>
Production rule of thumb
- Use polymorphic components for design-system primitives.
- Keep the typing pattern consistent across components.
- Don’t over-engineer: ship the 90% solution with good DX.
Interview questions
-
Q: How do you make props depend on
as?- A: Use generics +
ComponentPropsWithoutRef<T>and intersect with own props.
- A: Use generics +
-
Q: What’s the biggest practical risk?
- A: Making types too complex and hurting developer experience.
Quick recap
- Polymorphic components are a design-system pattern.
- Type with generics + inherited intrinsic props.
- Aim for good DX and maintainability.
Looking for structured, offline interview prep?
Free online lessons • Portable Amazon Kindle / Paperback
Frontend Interview Guide (2025 Edition)
By Rajesh Dhiman
Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.
JavaScript Interview Guide
By Rajesh Dhiman
Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.
Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.
Written by Rajesh Dhiman
Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.