Skip to main content

Overview

Composability is at the heart of Radix UI Primitives. Every component is designed to compose naturally, just like native HTML elements, giving you complete control over the DOM structure and behavior.
Radix components follow a 1-to-1 mapping principle: each component renders exactly one DOM element (unless explicitly documented otherwise).

1-to-1 DOM Mapping

The fundamental principle of composability in Radix is that each component maps to a single DOM node.

What This Means

When you write:
You get exactly this DOM structure:

Benefits

  1. Predictable - You know exactly what gets rendered
  2. Inspectable - DevTools show the real DOM structure
  3. Styleable - Direct access to elements for CSS
  4. Debuggable - No hidden wrapper elements to confuse you
Because each component renders one element, you can reason about the DOM structure by reading the JSX.

Exceptions Are Documented

When a component deviates from this pattern (like Dialog.Portal which renders to a different part of the DOM), it’s clearly documented with rationale.

Ref Forwarding

Refs are forwarded to the underlying DOM element, working exactly as you’d expect with native elements.

Basic Ref Usage

Now:
  • triggerRef.current is the <button> element
  • contentRef.current is the <div> element containing the dialog content

Accessing DOM Methods

Because refs point to real DOM nodes, you can use any DOM API:

Ref Composition

Radix uses the useComposedRefs hook internally to merge multiple refs. This is from packages/react/compose-refs/src/compose-refs.tsx:55:
This means internal refs (used for behavior) and your refs both work simultaneously.

The asChild Prop

The asChild prop is the key to advanced composition. It tells Radix to merge its functionality with your own element instead of rendering a default one.

Without asChild

Renders:

With asChild

Renders:
Radix merges its props (event handlers, aria attributes, etc.) with your element’s props. Your element’s props take precedence where appropriate.

How It Works

The asChild prop leverages Radix’s Slot component (from packages/react/slot/src/slot.tsx:45):
The Slot component:
  1. Takes the child element you provide
  2. Clones it with Radix’s props merged in
  3. Forwards refs correctly

Event Handler Composition

Event handlers compose automatically, allowing you to add custom logic without breaking built-in behavior.

Adding Your Own Handlers

Handler Execution Order

  1. Your handler runs first
  2. Radix’s internal handler runs second
  3. Event propagates unless stopped

Preventing Default Behavior

You can prevent Radix’s behavior when needed:

How It Works

Radix uses composeEventHandlers internally (from @radix-ui/primitive):
This ensures:
  • Your handler runs first
  • Radix respects preventDefault()
  • Event propagation works naturally

Composition Patterns

Pattern 1: Wrapping with Custom Components

Create your own components that wrap Radix:

Pattern 2: Polymorphic Components

Create components that can render as different elements:

Pattern 3: Animation Libraries

Compose with animation libraries like Framer Motion:

Pattern 4: Extending with Additional Props

Add your own props while preserving Radix functionality:

Prop Merging Behavior

When using asChild, props are merged intelligently:

Event Handlers

Both handlers run (yours first, then Radix’s):
Both myHandler and Radix’s internal handler execute.

Styles

Style objects are merged:
Radix’s styles (if any) merge with yours.

Class Names

Class names are concatenated:
Results in className="my-button [radix-classes]".

Other Props

Child props take precedence:
From packages/react/slot/src/slot.tsx:164:

Real-World Examples

Example 1: Custom Accordion Trigger

Example 2: Dialog with Custom Close Button

Example 3: Checkbox with Label Composition


Summary

Radix UI’s composability gives you:
  • Direct DOM access through 1-to-1 component mapping
  • Predictable refs that point to actual DOM elements
  • Flexible composition via the asChild prop
  • Natural event handling with automatic handler composition
  • Full control over rendering and behavior
Think of Radix components as enhanced HTML elements—they behave like native elements but with accessibility and interaction patterns built in.