Errors and fixes

Base UI and Radix: import paths and differences that break a migration

Radix and Base UI share neither paths nor APIs: one uses `asChild`, the other the `render` prop. Copying across breaks silently, not only on imports.

Key points

  • Radix imports from `@radix-ui/react-*`, Base UI from `@base-ui-components/react/*`.
  • Radix `asChild` maps to the Base UI `render` prop, not to an equivalent attribute.
  • Do not mix the two systems in one overlay tree: focus management differs.

Short answer: Radix and Base UI are different projects with different packages. Radix imports from @radix-ui/react-*, Base UI from @base-ui-components/react/*. The difference that breaks a migration is not the path: it is asChild against render.

Why two similar libraries are not interchangeable

Both projects come from the same line of descent: some of the people who wrote the Radix primitives now work on Base UI at MUI, with the same stated goal, accessible behaviour without styling. What changes is the composition model. Radix publishes one package per primitive and uses Slot to merge the child into the trigger, so the child receives props while the element stays yours. Base UI publishes one package with subpaths and delegates rendering to render, so the component owns the element and can hand state to it. The import path is the visible part of that difference.

The path map

ComponentRadixBase UI
Dialog@radix-ui/react-dialog@base-ui-components/react/dialog
Dropdown menu@radix-ui/react-dropdown-menu@base-ui-components/react/menu
Tabs@radix-ui/react-tabs@base-ui-components/react/tabs
Tooltip@radix-ui/react-tooltip@base-ui-components/react/tooltip

The composition difference

// Radix: il trigger si fonde con il figlio tramite asChild

  Apri


// Base UI: la prop render riceve l'elemento o una funzione
}>
  Apri

With asChild the child must be a single element that accepts props. With render the component delegates rendering and can take a function that receives state. The two models differ: copying the JSX without adapting it gives ignored props and buttons that open nothing.

How to check what is installed

# versioni installate dei due sistemi
npm ls @radix-ui/react-dialog @base-ui-components/react

# elenca ogni import che non risolve, con il percorso cercato
npx tsc --noEmit --pretty false

The compiler names the module it cannot find, not the file that imports it. That detail separates a wrong path from a missing dependency: in the first case the error points at a package that exists in node_modules but has a different subpath, in the second at the package alone. The most frequent case in the table above is dropdown-menu against menu: same component, different name.

When the fix does not work

If the component opens but focus never enters, the path is not the problem: the two systems handle focus differently. Do not nest a Radix overlay inside a Base UI overlay. The first focus trap then intercepts the second and the behaviour looks random.

When the usual fix makes things worse

The fix seen in real repositories is a wrapper that accepts asChild and forwards it to render, so the existing code keeps compiling. The wrapper works and hides a costlier fault: the state attributes are not the same. Radix exposes data-state="open", Base UI uses names of its own such as data-open. CSS written for the first system stops matching in silence, the popup opens with no position and no animation, and nothing appears in the console. An import error shows up in a minute; this one is found by opening the site.

The limit of the mapping

One limitation to budget for: the mapping is not complete. No declared equivalent exists for every part, and the sub-components do not follow a fixed scheme. Anyone holding a Radix wrapper that reads state from an attribute has to rewrite it, because with render the state arrives as a function argument. A migration is measured part by part against the documentation, not with a search and replace.

How to choose

  • If you start from shadcn, Radix is already wired in: switching costs effort with no immediate gain.
  • If you need the same primitive in several frameworks, Base UI cuts duplication.
  • Always check the API of the installed version: both projects are still moving.
  • Before migrating, search the CSS for state attributes: they break after the build has gone green again.

Where to continue

In the catalogue: components, dialog block and blocks. The guides: Radix, Base UI and React Aria and copy-paste or headless; the comparison: UI kit, library or design system.

Frequently asked questions

Can I swap a Radix component for a Base UI one and keep the props?

No. Component names, composition props and some state attributes all change.

Does asChild exist in Base UI?

No. The equivalent is the render prop, which takes an element or a function.

Can the two packages coexist?

Yes, but avoid nesting overlays across systems: focus traps and portals are not coordinated.

Which one should I pick for a new project?

It depends on the component ecosystem you already use. Starting from shadcn, Radix is wired in; for one API across frameworks, look at Base UI.

How do I check the bundle?

Every Base UI component is a separate module: import only what you use and compare the weight against the production build.