Polymorphic Components with 'as' Prop
Type the 'as' prop so a Button renders as or while preserving correct prop types.
Polymorphic Components with 'as' Prop is a free React Academy lesson on CoddyKit — lesson 3 of 4. 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 React Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is a Polymorphic Component?
A polymorphic component renders as different HTML elements or components based on an as prop — e.g., a Button that renders as <a> or <button> while keeping correct TypeScript types.
Simple as Prop (Without TypeScript)
A basic polymorphic implementation without generics — loses type safety but shows the concept.
function Box({ as: Tag = 'div', children, ...props }) {
return <Tag {...props}>{children}</Tag>;
}
// Usage:
<Box as="section" className="hero">...</Box>
<Box as="article">...</Box>Typed as Prop with Generics
Add a generic type parameter to infer the correct props for whatever element as renders as.
type PolymorphicProps<E extends React.ElementType> = {
as?: E;
} & Omit<React.ComponentPropsWithoutRef<E>, 'as'>;
function Box<E extends React.ElementType = 'div'>({
as,
...props
}: PolymorphicProps<E>) {
const Tag = as ?? 'div';
return <Tag {...props} />;
}With ref Support
Add forwardRef support while preserving the polymorphic typing.
type PolymorphicRef<E extends React.ElementType> = React.ComponentPropsWithRef<E>['ref'];
type PolymorphicPropsWithRef<E extends React.ElementType, P = {}> = P &
Omit<React.ComponentPropsWithRef<E>, keyof P> & { as?: E };
const Button = React.forwardRef(
<E extends React.ElementType = 'button'>(
{ as, ...props }: PolymorphicPropsWithRef<E>,
ref: PolymorphicRef<E>
) => {
const Tag = as ?? 'button';
return <Tag ref={ref} {...props} />;
}
);TypeScript Inference in Action
When you set as='a', TypeScript knows the component accepts anchor-specific props like href and target, but not button-specific ones like disabled.
// TypeScript allows href because as='a':
<Box as="a" href="https://example.com">Link</Box>
// TypeScript errors if you pass href to a div:
<Box as="div" href="...">Error!</Box> // TS: href is not a valid div propExtending with Custom Props
Add component-specific props alongside the polymorphic base by spreading them into the type definition.
type TextProps<E extends React.ElementType> = PolymorphicProps<E> & {
size?: 'sm' | 'md' | 'lg';
weight?: 'normal' | 'bold';
};
function Text<E extends React.ElementType = 'p'>({
as,
size = 'md',
weight = 'normal',
className,
...props
}: TextProps<E>) {
const Tag = as ?? 'p';
return (
<Tag
className={[`text-${size}`, `font-${weight}`, className].filter(Boolean).join(' ')}
{...props}
/>
);
}Real-World Use Case: Link that Renders as RouterLink
A Button that renders as React Router's Link when to is provided, or as a plain button otherwise.
import { Link } from 'react-router-dom';
type ButtonProps =
| { to: string; href?: never } & React.ComponentPropsWithoutRef<typeof Link>
| { href: string; to?: never } & React.ComponentPropsWithoutRef<'a'>
| { to?: never; href?: never } & React.ComponentPropsWithoutRef<'button'>;
function Button({ to, href, ...props }: ButtonProps) {
if (to) return <Link to={to} {...props} />;
if (href) return <a href={href} {...props} />;
return <button type="button" {...props} />;
}Design System Text Component
A polymorphic Text component that defaults to <p> but can render as h1–h6, span, or label.
<Text as="h1" size="xl">Page Title</Text>
<Text as="span" weight="bold">Inline bold</Text>
<Text as="label" htmlFor="email">Email</Text> // htmlFor is valid because as='label'Constraints on as Prop
Use extends React.ElementType to accept both HTML element strings ('div', 'a') and React components.
function Card<E extends React.ElementType = 'div'>({ as, ...props }: PolymorphicProps<E>) {
const Tag = as ?? 'div';
return <Tag {...props} />;
}
// Works with HTML elements:
<Card as="section" aria-label="Products" />
// Works with React components:
<Card as={motion.div} animate={{ opacity: 1 }} />Avoiding Common Pitfalls
Don't spread the as prop onto the element (it's not a valid HTML attribute). Always destructure it out before spreading rest props.
// Bad:
function Box({ as: Tag = 'div', ...props }) {
return <Tag as={...} {...props} />; // 'as' attribute on div is invalid HTML
}
// Good:
function Box({ as: Tag = 'div', ...props }) {
return <Tag {...props} />; // 'as' is used to pick Tag, not passed to element
}Performance Consideration
Polymorphic components have minimal performance overhead — the as prop is just a variable holding an element type string or component reference.
Quick Check
Why should you destructure the as prop before spreading rest props onto the element in a polymorphic component?
Recap
Polymorphic components use a generic as?: E extends React.ElementType prop to render as any element while preserving correct TypeScript props. Destructure as before spreading rest props. Use React.ComponentPropsWithoutRef to include all element-specific attributes in the type.
Frequently asked questions
Is the “Polymorphic Components with 'as' Prop” lesson free?
Yes — the full text of “Polymorphic Components with 'as' Prop” is free to read here on the web, and the React Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the React Academy course, upgrade to CoddyKit PRO.
What will I learn in “Polymorphic Components with 'as' Prop”?
Type the 'as' prop so a Button renders as or while preserving correct prop types. You practise React 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 React Academy?
No prior experience is required. React Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Polymorphic Components with 'as' Prop” 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 React Academy lesson?
Yes. Every React 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.
All lessons in this course
- Discriminated Unions for Component Variants
- Conditional & Mapped Types in React
- Polymorphic Components with 'as' Prop
- Type-Safe Forms & API Response Contracts