Errori e fix

Base UI e Radix: percorsi di import e differenze che rompono la migrazione

I due pacchetti non condividono né i percorsi né le API. Radix usa `asChild`, Base UI usa la prop `render`. Copiare un componente da un sistema all'altro produce errori silenziosi, non solo import rotti.

In sintesi

  • Radix importa da `@radix-ui/react-*`; Base UI da `@base-ui-components/react/*`.
  • `asChild` di Radix corrisponde alla prop `render` di Base UI, non a un attributo equivalente.
  • Non mescolare i due sistemi nello stesso albero di overlay: la gestione del focus è diversa.

Risposta breve: Radix e Base UI sono due progetti diversi con pacchetti diversi. Radix si importa da @radix-ui/react-*, Base UI da @base-ui-components/react/*. La differenza che rompe la migrazione non è il percorso: è asChild contro render.

Perché due librerie simili non sono interscambiabili

I due progetti vengono dallo stesso filone: parte di chi ha scritto le primitive Radix lavora oggi su Base UI dentro MUI, e l'obiettivo dichiarato è lo stesso, cioè comportamento accessibile senza stile. A cambiare è il modello di composizione. Radix pubblica un pacchetto per primitiva e usa Slot per fondere il figlio nel trigger: il figlio riceve le props mentre l'elemento resta tuo. Base UI pubblica un pacchetto con sottopercorsi e delega la resa a render, così il componente possiede l'elemento e può passargli lo stato. Il percorso è la parte visibile di questa differenza.

La mappa dei percorsi

ComponenteRadixBase UI
Dialog@radix-ui/react-dialog@base-ui-components/react/dialog
Dropdown@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

La differenza di composizione

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

  Apri


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

Con asChild il figlio deve essere un singolo elemento che accetta le props. Con render il componente delega la resa e può ricevere una funzione con lo stato. Sono due modelli diversi: copiare il JSX senza adattarlo produce props ignorate e pulsanti che non aprono nulla.

Come verificare cosa è installato

# 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

Il compilatore nomina il modulo che non trova, non il file che lo importa. È il dettaglio che distingue un percorso sbagliato da una dipendenza assente: nel primo caso l'errore punta a un pacchetto esistente nella cartella node_modules ma con un altro sottopercorso, nel secondo al pacchetto e basta. Il caso più frequente nella tabella qui sopra è dropdown-menu contro menu: stesso componente, nome diverso.

Quando la correzione non funziona

Se il componente si apre ma il focus non entra, non è un problema di percorso: i due sistemi gestiscono il focus in modo diverso. Non annidare overlay Radix dentro overlay Base UI. In quel caso il focus trap del primo intercetta il secondo e il comportamento sembra casuale.

Quando la correzione tipica peggiora le cose

La correzione che si vede nei repository è un wrapper che accetta asChild e lo gira su render, così il codice esistente continua a compilare. Il wrapper funziona e nasconde un guasto più caro: gli attributi di stato non sono gli stessi. Radix espone data-state="open", Base UI usa nomi propri come data-open. Il CSS scritto per il primo sistema smette di corrispondere nel silenzio, il popup si apre senza posizione e senza animazione, e nessun errore compare in console. Un errore di import si vede in un minuto, questo si trova aprendo il sito.

Il limite della mappatura

Un limite da mettere in conto: la mappatura non è completa. Non esiste un equivalente dichiarato per ogni parte, e i sotto-componenti non seguono uno schema fisso. Chi ha un wrapper Radix che legge lo stato da un attributo deve riscriverlo, perché con render lo stato arriva come argomento della funzione. La migrazione si misura parte per parte sulla documentazione, non con una ricerca e sostituzione.

Come scegliere

  • Se parti da shadcn, Radix è già integrato: cambiare costa senza un guadagno immediato.
  • Se ti serve la stessa primitiva in più framework, Base UI riduce la duplicazione.
  • Verifica sempre le API della versione installata: entrambi i progetti sono in evoluzione.
  • Prima di migrare, cerca nel CSS gli attributi di stato: sono la parte che rompe dopo che la build è tornata verde.

Dove continuare

Nel catalogo: componenti, blocco dialog e blocchi. Le guide: Radix, Base UI e React Aria e copy-paste o headless; il confronto: UI kit, libreria o design system.

Domande frequenti

Posso sostituire un componente Radix con uno Base UI lasciando le props?

No. Cambiano i nomi dei componenti, le props di composizione e alcuni attributi di stato.

`asChild` esiste in Base UI?

No, l'equivalente è la prop `render`, che riceve un elemento o una funzione.

I due pacchetti possono convivere?

Sì, ma evita overlay annidati tra sistemi diversi: focus trap e portali non sono coordinati.

Quale scegliere per un progetto nuovo?

Dipende dall'ecosistema dei componenti che usi. Se parti da shadcn, Radix è già integrato; se vuoi un'API unica per più framework, valuta Base UI.

Come verifico il Bundle?

Ogni componente Base UI è un modulo separato: importa solo quello che usi e confronta il peso con la build di produzione.