Skip to content

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.

Rajesh DhimanMarch 01, 20262 min read
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 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.
Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback
JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

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.