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.

F

Frontend Interview Team

March 01, 2026

2 min read
Polymorphic React Components (the 'as' Prop) with Type Safety

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 error

A 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 as is.
  • 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:

  • forwardRef
  • ComponentPropsWithRef<T>
  • a PolymorphicRef<T> helper

That’s great content for a separate “29b” lesson if you want ultra-depth.


Common mistakes

  • React.FC for polymorphic components (usually makes things worse)
  • Manually unioning props (button | a | div | ...)—doesn’t scale
  • Losing href/type correctness by typing everything as HTMLAttributes<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

  1. Q: How do you make props depend on as?

    • A: Use generics + ComponentPropsWithoutRef<T> and intersect with own props.
  2. 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.