Skip to main content

What is Composition?

Radix UI Primitives are built with a composable API design. This means components are designed to be combined and nested to create complex UI patterns while maintaining full control over the rendered markup. The key principle: one component renders one DOM element (or no DOM element at all).

The Composable API

Instead of a single component with many props, Radix provides multiple sub-components that work together:

Benefits of Composition

Flexibility

Insert your own elements anywhere in the component tree. Add wrappers, change order, or customize markup.

Control

Direct access to each DOM element means full control over styling, event handlers, and attributes.

Predictability

Easy to understand what HTML will be rendered. One component = one DOM element.

Simplicity

No complex prop configurations. Compose the pieces you need like building blocks.

1-to-1 Mapping

Each Radix component maps directly to a single DOM element:
This 1-to-1 mapping means:
  • Refs work as expected - Forward a ref to a Radix component, and it points to the actual DOM node
  • Event handlers work naturally - Add onClick, onMouseEnter, etc., directly to components
  • Styling is straightforward - Apply classes or styles exactly where you need them
  • Inspection is easy - The React component tree matches the DOM tree
Some components like Dialog.Root and Accordion.Root don’t render any DOM element - they only manage state and context.

Composition Patterns

Basic Composition

Nest components to build the structure you need:

Adding Custom Elements

You can add your own elements anywhere:

Conditional Rendering

Components work naturally with conditional rendering:

Mapping Over Data

Easily create dynamic lists:

The asChild Prop

One of the most powerful composition features is the asChild prop. It allows you to merge Radix’s functionality with your own elements.

Without asChild

By default, Radix components render their own element:

With asChild

Use asChild to merge functionality into your own element:
The asChild prop uses the Slot component under the hood to merge props, refs, and event handlers.

Using with Custom Components

You can use asChild with your own components:

Using with Icons and Wrappers

Accessing DOM Refs

Since components map 1-to-1 with DOM elements, refs work naturally:

Event Handler Composition

You can add your own event handlers, and they’ll be composed with Radix’s internal handlers:
Event handlers are composed, not replaced. Both your handler and Radix’s internal handler will run.

Controlled vs Uncontrolled

Composition works with both controlled and uncontrolled patterns:

Uncontrolled (Internal State)

Controlled (External State)

Building Wrapper Components

You can create your own wrapper components while maintaining composability:
When building wrapper components, make sure to forward refs and preserve accessibility props.

Composition Anti-Patterns

Avoid these common mistakes:

Don’t Skip Required Parts

Don’t Break Component Hierarchy

Composition vs Configuration

Radix favors composition over configuration: Composition wins because:
  • More flexible - you control the markup
  • More predictable - you see exactly what renders
  • Better TypeScript support - each component has specific types
  • Easier to customize - no need to learn complex prop APIs

Next Steps

Accessibility

Learn about accessibility features

Slot Component

Deep dive into the Slot utility

Components

Browse all available components

Styling

Learn about styling approaches