Skip to main content
react

Radix, React Aria, Base UI, and the Headless Component Question

Radix, React Aria, and Base UI each make different trade-offs. A concrete comparison of the three headless component libraries and when to reach for each.


6 min read

Radix UI, React Aria, and Base UI all solve the same core problem: separate interactive component behavior and accessibility from visual presentation. You bring your own styles. But they make very different tradeoffs, and picking the wrong one costs you weeks. The short answer: Radix for most product work, React Aria when you have international users or strict WCAG AA requirements, Base UI if you are building something Tailwind-native and can live with a library in active development.


Radix UI

Radix is uncontrolled-first, meaning it manages its own state by default and optionally accepts open, onOpenChange and similar controlled props. This is a practical choice — most dialogs and dropdowns do not need external state management. The API surface is small and opinionated.

import * as Dialog from '@radix-ui/react-dialog'

<Dialog.Root>
  <Dialog.Trigger asChild>
    <button>Open</button>
  </Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Overlay className="fixed inset-0 bg-black/50" />
    <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white p-6">
      <Dialog.Title>Title</Dialog.Title>
      <Dialog.Close>Close</Dialog.Close>
    </Dialog.Content>
  </Dialog.Portal>
</Dialog.Root>

The asChild prop is Radix's best feature — it merges props and behavior onto whatever element you provide, avoiding extra DOM nodes. The Portal component handles the z-index stacking issue for you.

What Radix does not guarantee: Full WCAG 2.1 AA compliance in all cases. Some components lack aria-describedby wiring. Internationalization (RTL layout, locale-aware keyboard navigation) is not built in. For most English-language products this is not a problem. For anything shipping in Arabic, Hebrew, or Persian it matters.

React Aria

Adobe's React Aria is the most rigorous accessibility implementation available in open source React. It is also the most complex API. The library is built around hooks that return prop collections — you spread them onto your elements.

import { useDialog, useOverlay, FocusScope } from 'react-aria'
import { useRef } from 'react'

function Dialog({ title, children, onClose }) {
  const ref = useRef(null)
  const { overlayProps } = useOverlay({ isOpen: true, onClose, isDismissable: true }, ref)
  const { dialogProps, titleProps } = useDialog({}, ref)

  return (
    <FocusScope contain restoreFocus autoFocus>
      <div {...overlayProps} {...dialogProps} ref={ref}>
        <h3 {...titleProps}>{title}</h3>
        {children}
      </div>
    </FocusScope>
  )
}

This is more code than Radix for the same component. The tradeoff is explicit control — every prop is visible, every ARIA attribute is traceable, every interaction model is documented in the spec.

React Aria ships internationalization for free: RTL support, locale-aware date pickers, number formatting. The @react-aria/calendar component handles 20+ calendar systems. If your product sells globally, this is built-in work you would otherwise write yourself.

The newer react-aria-components package offers a compound component API closer to Radix, reducing the hook-spreading overhead. Still more verbose than Radix, but substantially better than the raw hooks API.

Base UI

Base UI is MUI's answer to "what if we extracted the behavior layer from Material UI and made it framework-agnostic." It is in active development (currently RC-stage as of mid-2026) and Tailwind-native from the start — no CSS-in-JS, no sx prop, no emotion.

The API is clean:

import { Dialog } from '@base-ui-components/react/dialog'

<Dialog.Root>
  <Dialog.Trigger>Open</Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Backdrop className="fixed inset-0 bg-black/50" />
    <Dialog.Popup className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white p-6">
      <Dialog.Title>Title</Dialog.Title>
      <Dialog.Close>Close</Dialog.Close>
    </Dialog.Popup>
  </Dialog.Portal>
</Dialog.Root>

This is almost identical to Radix's API, intentionally. Base UI adds a render prop where Radix uses asChild, which is a cleaner API — render={<button />} instead of asChild with a child element.

The catch is ecosystem maturity. Radix has three years of production use, a large community, and shadcn/ui building on top of it. Base UI has MUI's engineering behind it but fewer real-world battle reports. Components are being added incrementally and some gaps exist.

Comparison at a glance

RadixReact AriaBase UI
API styleCompound componentsHooks + compound (new)Compound components
WCAG AAPartialFullPartial
i18n / RTLNoYesPartial
Tailwind-firstCommunity (shadcn)NoYes
StabilityStableStableRC
asChild-style APIasChild proprender (new)render prop

Which one I actually reach for

Radix, for most work. The API is small, the community tooling (shadcn, Radix Themes) is mature, and WCAG partial coverage is sufficient for most products if you layer tests on top. For B2B products with enterprise accessibility requirements or government contracts, React Aria. For a new greenfield project with Tailwind and no legacy component decisions, I would now give Base UI a serious look — the API is better than Radix's asChild pattern and MUI's track record on long-term maintenance is good.