0Pricing
TypeScript Academy · Lesson

Polymorphic components; as props

Build a polymorphic Button component with an as prop; merge intrinsic/DOM props; type refs using forwardRef and ComponentRef .

Polymorphic components; as props is a free TypeScript Academy lesson on CoddyKit — lesson 1 of 1. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 1 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Intro

Goal: Create a single polymorphic Button that can render as button, a, or any element via an as prop—while preserving prop and ref types.

  • Constrain T to React.ElementType
  • Merge props safely
  • Type the forwarded ref

Base types

Start with OwnProps and a utility PolymorphicProps<T, P> that merges intrinsic props for T with your own.

import React from "react"

type OwnProps = {
  variant?: "solid" | "outline"
  children: React.ReactNode
}

type PolymorphicProps<T extends React.ElementType, P> = {
  as?: T
} & P & Omit<React.ComponentPropsWithoutRef<T>, keyof P | "as">

// Example: Omit avoids prop collisions (e.g., our OwnProps vs intrinsic props)

Polymorphic Button

The component defaults to button but accepts as. The ref type changes with T (e.g., HTMLAnchorElement for "a").

import React, { forwardRef } from "react"

type OwnProps = { variant?: "solid" | "outline"; children: React.ReactNode }

type PolymorphicProps<T extends React.ElementType, P> = { as?: T } & P & Omit<React.ComponentPropsWithoutRef<T>, keyof P | "as">

const ButtonInner = <T extends React.ElementType = "button">(
  { as, variant = "solid", children, ...rest }: PolymorphicProps<T, OwnProps>,
  ref: React.ForwardedRef<React.ComponentRef<T>>
) => {
  const Component = (as || "button") as React.ElementType
  const style = variant === "outline" ? { border: "1px solid #333", padding: 8 } : { background: "#2563eb", color: "white", padding: 8, borderRadius: 6 }
  return <Component ref={ref} style={style} {...rest}>{children}</Component>
}

export const Button = forwardRef(ButtonInner) as <T extends React.ElementType = "button">(
  props: PolymorphicProps<T, OwnProps> & { ref?: React.Ref<React.ComponentRef<T>> }
) => JSX.Element

Usage examples

When as="a", intrinsic props like href become available and the ref is typed as HTMLAnchorElement.

import React, { useRef } from "react"
import { Button } from "./Button"

export default function App() {
  const btnRef = useRef<HTMLButtonElement>(null)
  const linkRef = useRef<HTMLAnchorElement>(null)

  return (
    <div style={{ display: "grid", gap: 8 }}>
      <Button ref={btnRef} onClick={() => alert("clicked")}>Save</Button>
      <Button as="a" ref={linkRef} href="#docs" variant="outline">Docs</Button>
    </div>
  )
}

Variants

You can wrap the polymorphic component and still keep the flexibility. Use ComponentProps<typeof Button> to inherit its props.

import React from "react"
import { Button } from "./Button"

export function DangerButton(props: Omit<React.ComponentProps<typeof Button>, "variant">) {
  return <Button {...props} style={{ background: "#dc2626", color: "white", padding: 8, borderRadius: 6 }} />
}

// Works with polymorphism too
export default function App() {
  return (
    <div style={{ display: "grid", gap: 8 }}>
      <DangerButton>Delete</DangerButton>
      <DangerButton as="a" href="#confirm">Delete link</DangerButton>
    </div>
  )
}

Tips

Tips:

  • Constrain T to React.ElementType.
  • Use Omit to resolve prop name collisions.
  • Forward the ref with forwardRef and type it as React.ComponentRef<T>.

Polymorphic typing check

Quick check: Which typing correctly models a polymorphic component that forwards its ref?

Recap

Recap: You built a polymorphic Button with an as prop, safely merged intrinsic props, and typed the forwarded ref so consumers get correct intellisense.

Frequently asked questions

Is the “Polymorphic components; as props” lesson free?

Yes — the full text of “Polymorphic components; as props” is free to read here on the web, and the TypeScript Academy course includes 1 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.

What will I learn in “Polymorphic components; as props”?

Build a polymorphic Button component with an as prop; merge intrinsic/DOM props; type refs using forwardRef and ComponentRef . You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start TypeScript Academy?

No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 1, so you can start here or from the beginning and move at your own pace.

How long does the “Polymorphic components; as props” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this TypeScript Academy lesson?

Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

← Back to TypeScript Academy