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.
Frontend Interview Team
March 01, 2026
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.