React Curriculum

Polymorphic Components & The "as" Prop Pattern

Hard100% Free~30 mins#react#patterns#polymorphic#as-prop#design-systems#primitives
Interview Answer (30-Second Summary)

A Polymorphic Component is a dynamic UI primitive that can render as different HTML tags or React components using an "as" prop, while preserving native semantic HTML attributes, accessibility, and visual styling without code duplication.

Key Learning Objectives
✓

Design flexible polymorphic UI primitives (Button, Text, Box) using the "as" prop pattern.

✓

Preserve semantic HTML for accessibility (e.g. rendering a button visually while outputting an <a> anchor tag).

✓

Forward refs cleanly to dynamic underlying DOM elements.

✓

Explain how TypeScript handles type inference for dynamic HTML attributes based on the "as" prop.

The Interview Problem & Context

Implement a polymorphic <Button> component that defaults to `<button>` but can render as `<a>`, forwarding refs and native HTML attributes properly.

1import React, { ElementType, forwardRef } from 'react';
2
3// Polymorphic props type definition
4type PolymorphicProps<E extends ElementType, P = {}> = P & {
5 as?: E;
6} & Omit<React.ComponentPropsWithRef<E>, keyof P | 'as'>;
7
8interface ButtonBaseProps {
9 variant?: 'primary' | 'secondary' | 'danger';
10 children: React.ReactNode;
11}
12
13// Polymorphic component definition
14export const Button = forwardRef(function Button<E extends ElementType = 'button'>(
15 { as, variant = 'primary', children, ...restProps }: PolymorphicProps<E, ButtonBaseProps>,
16 ref: any
17) {
18 // 1. Dynamic component tag assignment
19 const Component = as || 'button';
20
21 return (
22 <Component
23 ref={ref}
24 className={`btn btn-${variant}`}
25 {...restProps}
26 >
27 {children}
28 </Component>
29 );
30});
31
32// Example Usage:
33export function Navigation() {
34 return (
35 <div className="nav-actions">
36 {/* 1. Renders as native <button> */}
37 <Button variant="primary" onClick={() => alert('Clicked')}>
38 Submit Action
39 </Button>
40
41 {/* 2. Renders as native <a> with href, target */}
42 <Button as="a" href="https://example.com" target="_blank" variant="secondary">
43 External Link
44 </Button>
45 </div>
46 );
47}
Deep Architectural Explanation

Why Polymorphic Components are Essential

In modern design systems, visual appearance often diverges from semantic HTML:

  • A link needs to look like a prominent button.
  • A button needs to look like plain text.
  • If developers misuse <button onClick={() => window.location.href = ...}>, users lose middle-click opening, right-click "Open in new tab", bookmarking, and screen readers announce a button instead of a link!

How the Pattern Works:

  1. Dynamic Element Resolution: JSX requires custom element types to be capitalized (<Component />). We assign const Component = as || 'button'.
  2. Prop Forwarding: All remaining props (href, target, type, onClick) are spread onto the underlying element.
  3. Accessibility Win: Visual styles remain 100% consistent via CSS classes, while the rendered DOM tree uses the semantically correct HTML tag.
Common Interview Traps & Pitfalls
✕

Lowercasing the dynamic tag variable in JSX (`<as />`), which React compiles as a literal `<as>` HTML tag rather than evaluating the variable.

✕

Creating fake buttons that navigate via JavaScript instead of using `as="a"`.

✕

Losing type safety by using `any` for props instead of generic polymorphic types.

Senior Interviewer Follow-Up Probes
?

How do modern libraries like Radix UI implement polymorphic behavior using the `asChild` Slot pattern instead?

?

How do you write the complete TypeScript type with ref forwarding without using `any`?

?

What are the performance impacts of dynamic element creation in large lists?

Frequently Asked Interview Questions
What is a polymorphic component in React?

A component that can dynamically render as different HTML elements or other React components via an "as" prop, while maintaining its styling, functionality, and type safety.

It ensures interactive elements use semantic HTML tags. Navigation controls can render as <a> anchors (supporting middle-click and screen readers) while retaining button visual design.

JSX treats lowercase tags (like <div> or <as>) as built-in HTML strings. Uppercase tags (<Component>) tell JSX to look up a JavaScript variable or component in scope.

The "asChild" slot pattern, where rather than passing a tag name string, the consumer passes the desired element as a child, and the parent clones it while merging props and event handlers.

Yes. You can pass custom components like Next.js Link or React Router Link (`as={Link}`), and the polymorphic component will forward props to that component.

The ref type must be parameterized generically with the element type E, so that the forwarded ref matches the specific DOM element (e.g. HTMLAnchorElement vs HTMLButtonElement).