DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
World desk6 min

Polymorphic React Components in TypeScript: `as` vs. `asChild`

An `as` prop lets a wrapper choose its target; Radix `asChild` composes a primitive onto a caller-supplied child. Learn the typing, ref, and accessibility trade-offs.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use an as prop when a component should choose its rendered element from a prop, and use asChild when a caller should provide the element that receives a primitive’s props and behavior. Neither is a built-in React API: as is a component-design pattern, while asChild is documented by Radix as a composition pattern based on cloning and merging props. The right choice depends on who owns the target and whether that target preserves the behavior and accessibility your component requires.

What do `as` and `asChild` mean?

Both patterns let a reusable component render something other than its default element, but they put the choice in different hands.

As an Amazon Associate I earn from qualifying purchases.

  • as: The wrapper chooses the target through a prop, such as <Button as="a" href="/docs">. The component implementation renders that target and passes it props.
  • asChild: The caller supplies the child element, and the primitive composes onto it. In Radix, a component such as a Tooltip trigger can omit its default element, clone its child, and merge the props and behavior it needs onto that child.

Radix documents asChild for primitive parts that render DOM elements. Its Tooltip trigger, for example, defaults to a button but can compose onto an anchor. See the Radix composition guide and Radix Slot documentation for the library’s behavior and API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How do I type a polymorphic React component with an `as` prop?

Type the target and its props together. A common design is to make the target a generic parameter, derive the props accepted by that target, remove any keys that the wrapper defines itself, then add the wrapper’s own props. This is a practical pattern rather than an official React or TypeScript standard utility; the React and Radix documentation do not prescribe a single canonical polymorphic type.

Example: a button-like component targeting an anchor

This React 19-oriented sketch shows the type relationship. It supports intrinsic elements such as button and a; a library that also supports arbitrary custom components should add and test an appropriate component-target type.

import type { ComponentPropsWithRef, ElementType, ReactNode } from "react";

type ButtonOwnProps = {
  tone?: "primary" | "quiet";
  children: ReactNode;
};

type PolymorphicProps<T extends ElementType> =
  ButtonOwnProps &
  { as?: T } &
  Omit<ComponentPropsWithRef<T>, keyof ButtonOwnProps | "as">;

function Button<T extends ElementType = "button">(
  { as, tone = "primary", ...props }: PolymorphicProps<T>
) {
  const Tag = as ?? "button";
  return <Tag data-tone={tone} {...props} />;
}

<Button type="button">Save</Button>;
<Button as="a" href="/docs">Read the docs</Button>;

The generic target lets TypeScript infer the target-specific props: an anchor target can accept href, while a button target can accept button attributes. The Omit prevents collisions between target props and wrapper-owned props from producing an ambiguous public API.

Decide prop ownership and collisions

When the wrapper and target both define a prop name, decide which meaning wins and encode it in the type and implementation. In the sketch, wrapper-owned keys are omitted from the target-derived props, and the wrapper controls tone. Be explicit about this rule in public component documentation. The spread order also matters: props applied after the spread can override caller values, while props before it can be overridden by the caller.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
TypeScript Programming Language - Software Engineer & Coder T-Shirt
  • TypeScript implements a superset of syntax for strictly typed development, facilitating deep static analysis and enhanced development environment integration. The compiler translates source into standard script formats, ensuring parity across any runtime.
  • TypeScript is ideal for front-end developers, full-stack engineers, and software architects who build large-scale web applications. It serves those looking to improve code excellence, reduce bugs through static checking, and maintain complex projects more.
  • Lightweight, Classic fit, Double-needle sleeve and bottom hem

Keep supported targets meaningful

A type that accepts every possible component is not automatically a sound component API. Limit targets to those that can honor the wrapper’s contract. A control that promises button behavior should not silently become a non-interactive element just because its type permits it. For broad library APIs, test inference for the default target, alternate intrinsic targets, custom components, and ref types against the exact React and @types/react versions you support.

How does Radix `asChild` composition work?

With Radix asChild, the primitive uses its child in place of its default DOM element and merges its required props and behavior onto that child. For a wrapper component, the documented basic pattern is to render Slot.Root when asChild is true and the normal element otherwise. If the wrapper has multiple children, Radix documents Slottable to mark which child should receive the merged props. Check the Slot documentation for details and confirm its API against the version installed in your project; the cited Slot page identifies version 1.3.0.

The supplied child must work as the target, not just look like one. A custom child component needs to accept and pass through the props supplied by the primitive, as well as the ref when the primitive needs one. If it drops those props, the primitive’s event handlers, accessibility attributes, or other behavior may not reach the rendered DOM node.

Forward props and refs from custom children

For React 18 and earlier, Radix’s composition guide demonstrates React.forwardRef for leaf components that may be used under asChild. The component should spread received props onto the intended DOM element and attach the ref there.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import * as React from "react";

type LinkProps = React.ComponentPropsWithoutRef<"a">;

const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
  (props, ref) => <a {...props} ref={ref} />
);

Link.displayName = "Link";

This example targets the earlier ref pattern. React 19 changes how function components receive refs, so do not copy a ref type and implementation across React majors without checking the versions your package supports.

How do refs work in React 19?

In React 19, a function component can read ref as a prop, and new function components no longer need forwardRef. React’s current reference marks forwardRef deprecated in React 19 in favor of passing ref as a prop. For earlier React versions, forwardRef remains the documented compatible approach. See the React 19 upgrade guide and React forwardRef reference.

React treats key and, historically, ref specially rather than as ordinary props. React 19’s function-component ref support changes that ref guidance, but does not make key a normal prop. The upgrade guide also covers TypeScript changes, including use of the scoped React.JSX namespace rather than relying on a global JSX namespace. Keep component declarations, runtime code, and installed React types aligned with the React major you target.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Which should I use for a Button: `as` or `asChild`?

Choose based on who should control the rendered element and how the component’s behavior reaches it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Design question as asChild
Who chooses the target? The wrapper caller selects it through the as prop. The caller supplies a child; the primitive composes onto that child.
How do props reach it? The wrapper renders the target and passes props through its implementation. Radix Slot clones the immediate child and merges props onto it.
What is the central type concern? Connect the selected target to its target-derived props, while resolving wrapper-owned name collisions. The child must accept injected props and any needed ref; the wrapper can check for a child element but cannot guarantee every custom child forwards correctly.
What needs careful review? Whether each supported target matches the component’s promised semantics and behavior. Whether the supplied child forwards props and refs and preserves the primitive’s required interaction behavior.

Use as when the wrapper should own the target choice and offer a target-aware API. Use asChild when callers already have the element or component they want to render and the primitive should add its behavior to it. Neither pattern is universally better; Radix documents its own composition API, not a rule for every design system.

What accessibility and behavior must the target preserve?

The rendered target must support the interaction the component claims to provide. Radix warns that changing a focusable trigger into a div can make it inaccessible. Its composition guide puts responsibility on the author changing the underlying element to keep it accessible and functional.

For a trigger, check that the resulting element is focusable and responds to the pointer and keyboard events the primitive requires. For a control presented as a button, prefer a real button unless the API intentionally supports another semantic element and implements the expected behavior. For an anchor, provide a meaningful destination when navigation is intended. TypeScript can help express prop compatibility, but it cannot establish that a target’s runtime semantics are correct.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.