Understanding React's as Prop and Radix UI's asChild Prop
When working with component libraries in React, you might have come across the as
prop or Radix UI's asChild
prop.
These props provide a powerful way to make components more flexible and composable. We'll dive into their differences, how they work, and when to use them.
React's as
Prop
The as
prop is a pattern commonly used in component libraries to allow developers to dynamically change the rendered element of a component.
Instead of wrapping a component in multiple layers of divs or spans, you can control which HTML tag or component is used under the hood.
Here's an example of how the as
prop works in a simple button component:
import React from "react";type ButtonProps = { as?: React.ElementType; children: React.ReactNode;} & React.ComponentPropsWithoutRef<"button">;const Button: React.FC<ButtonProps> = ({ as: Component = "button", children, ...props }) => { return <Component {...props}>{children}</Component>;};export default function App() { return ( <> <Button>Default Button</Button> <Button as="a" href="#">Rendered as Anchor</Button> </> );}
The simplified Button
component above accepts an as
prop that determines the underlying element type.
When the as
prop is not provided, it defaults to rendering a button element.
// A simpler version of our polymorphic buttontype ButtonProps = { as?: React.ElementType; // This allows changing the rendered element children: React.ReactNode; variant?: 'primary' | 'secondary';} & React.ComponentPropsWithoutRef<"button">; // Inherit standard button propsconst Button = ({ as: Component = "button", // Default to button if no element specified children, variant = 'primary', ...props // Spread remaining props to the rendered element}: ButtonProps) => { // Simple className based on variant const className = `button ${variant}`; return ( <Component className={className} {...props}> {children} </Component> );};
Why Use the as
Prop?
-
It allows customization of the underlying element without losing functionality.
-
Helps avoid unnecessary wrappers and keeps semantic HTML intact.
-
Works well with accessibility (e.g., using
as="a"
with appropriate attributes for navigation).
Radix UI's asChild
Prop
Radix UI introduces the asChild
prop, which behaves differently from the as
prop.
Instead of changing the rendered element type, it replaces the component's wrapping element with the provided child component.
import { Dialog, DialogTrigger, DialogContent, DialogTitle } from "@radix-ui/react-dialog";import { Button } from "./Button"; // Assuming we have a custom Button componentexport default function AsChildExample() { return ( <div className="example"> {/* Standard usage - DialogTrigger renders its own button */} <Dialog> <DialogTrigger>Open Dialog</DialogTrigger> <DialogContent> <DialogTitle>Standard Dialog</DialogTitle> <p>This uses the default DialogTrigger button</p> </DialogContent> </Dialog> {/* With asChild - composition pattern */} <Dialog> <DialogTrigger asChild> <Button variant="primary" size="large">Custom Open Dialog</Button> </DialogTrigger> <DialogContent> <DialogTitle>Custom Dialog</DialogTitle> <p>This uses our custom Button component as the trigger</p> </DialogContent> </Dialog> </div> );}
The simplified DialogTrigger
component with asChild
support might look like this:
type DialogTriggerProps = { asChild?: boolean; children: React.ReactNode;};const DialogTrigger: React.FC<DialogTriggerProps> = ({ asChild, children, ...props }) => { // When asChild is true, clone the child and merge the props if (asChild && React.isValidElement(children)) { return React.cloneElement(children, { ...props, ...children.props, }); } // Default behavior: render a button with the provided props return ( <button type="button" {...props}> {children} </button> );};
Why Use the asChild
Prop?
-
It enables true component composition, allowing you to leverage your existing component library.
-
Maintains proper accessibility attributes and event handling across component boundaries.
-
Allows you to customize the look and feel of primitive components without sacrificing their built-in functionality.
-
Provides better type safety when used with TypeScript compared to manual prop forwarding.
Conclusion
Both as
and asChild
are valuable tools for building flexible, composable React components.
The as
prop lets you change the rendered element while preserving the component structure, whereas asChild
fully replaces the wrapper element.