← Blog

O shadcn/ui mudou para Base UI. Isto é o que muda de verdade no seu código

O shadcn/ui agora usa Base UI por padrão em vez de Radix. Toda mudança de API que você vai encontrar, a saída de emergência -b radix e uma ordem de migração que não dói.

Preet Suthar · · 5 min de leitura

  • React
  • shadcn/ui
  • Base UI
  • Radix

Em 3 de julho de 2026, npx shadcn init passou a escolher Base UI em vez de Radix. Se você leu as manchetes e nada mais, provavelmente pensa uma de duas coisas: que o Radix morreu, ou que nada mudou. Nenhuma das duas é verdade.

Eu mantenho o HextaUI, um registry shadcn/ui construído sobre Base UI, então passei a maior parte deste ano dentro das duas camadas de primitivas. Este é o guia que eu queria que existisse em julho: o que mudou, o que não mudou, toda diferença de API que você vai encontrar e a ordem para migrar, se você decidir migrar.

O que mudou, em um parágrafo

Projetos shadcn/ui novos recebem primitivas do Base UI. O Radix continua totalmente suportado. O time do shadcn disse que toda atualização e todo componente novo saem para as duas bibliotecas, a menos que um componente só exista no Base UI. Projetos existentes ficam intocados. Se você quer Radix em um projeto novo, passa uma flag:

npx shadcn@latest init -b radix

Se você tem CI ou scripts que chamam init de forma não interativa e esperam Radix, adicione essa flag agora, antes que um checkout novo te surpreenda.

Por que o padrão mudou

Dois motivos foram dados. O Base UI tinha chegado a uma versão estável 1.6.0 com mais de seis milhões de downloads semanais, e os projetos criados pelo shadcn/create já escolhiam Base UI em vez de Radix em uma proporção de cerca de dois para um. O padrão seguiu o que as pessoas estavam escolhendo.

O Base UI é feito pelas pessoas por trás do Radix, do Material UI e do Floating UI, e a linhagem aparece. Ele cobre primitivas que o Radix nunca lançou, como Combobox, Autocomplete, Number Field, Toast e Preview Card. Em setembro de 2026 ele está na versão 1.8.0.

Você deve migrar um app existente?

Provavelmente não neste trimestre, e não tudo de uma vez.

Fique no Radix se o app está estável, você não está travado por falta de uma primitiva e ninguém no time tem uma reclamação específica. Nada do que você depende vai sumir.

Migre se você precisa de Combobox ou Number Field sem recorrer a uma segunda biblioteca, se já está reescrevendo os componentes para o Tailwind v4 de qualquer forma, ou se quer estar na camada que os novos componentes shadcn/ui miram primeiro.

De qualquer forma, leia a próxima seção antes de decidir, porque as diferenças não são cosméticas.

Toda diferença de API que você vai encontrar de verdade

Agrupei estas pelo jeito que falham. O primeiro grupo quebra o seu build. O segundo compila normalmente e muda o comportamento em silêncio, o que é pior.

Mudanças que quebram o build

asChild vira render. Esta é a que todo mundo encontra primeiro.

// Radix
<DialogTrigger asChild>
  <Button>Open</Button>
</DialogTrigger>

// Base UI
<DialogTrigger render={<Button />}>Open</DialogTrigger>

A prop render aceita um elemento ou uma função. A forma de função te dá o estado, o que é útil para coisas como um gatilho que troca o ícone quando aberto.

Popups ancorados ganham um Positioner. Menu, Select, Popover e Tooltip passam de Portal > Content para Portal > Positioner > Popup. Dialog não tem Positioner; ele usa Backdrop e Popup.

Overlay agora é Backdrop. Renomeie e siga em frente.

Os valores de Select são anuláveis. Um Select controlado guarda Value | null, não string. O seu useState<string>("") vira useState<string | null>(null), e todo lugar que lia o valor como string precisa de uma proteção.

Os valores de Accordion e Toggle Group são sempre arrays. Mesmo no modo único, value="a" vira value={["a"]}. Tabs mantém um valor único.

onOpenChange tem um segundo argumento. O Radix deixa você chamar event.preventDefault() para impedir um fechamento. O Base UI passa (open, details) e você chama details.cancel(). O campo details.reason diz se foi a tecla Escape, um clique fora ou outra coisa, o que é útil para diálogos de "alterações não salvas".

Os seletores de estado aberto mudam. As variantes data-[state=open]: viram data-open:. Esta se esconde nas suas strings de className e nas variantes do cva, então faça um grep por data-[state= antes de dar a migração por concluída.

Mudanças silenciosas de comportamento

Tabs ativam manualmente por padrão. No Radix, as setas movem o foco e ativam a aba. No Base UI, as setas movem o foco e você pressiona Enter ou Espaço para ativar. Se seus testes ou usuários esperam o comportamento do Radix, defina activateOnFocus na lista.

Itens de menu checkbox e radio mantêm o menu aberto. closeOnClick tem false como padrão para CheckboxItem e RadioItem. Itens comuns continuam fechando. Pode-se argumentar que é o padrão melhor, mas é uma mudança que seus usuários vão notar.

Menus de navegação abrem mais rápido. O delay de hover do Base UI tem 50ms como padrão. O delayDuration do Radix tinha 200ms como padrão. Menus rápidos parecem ótimos até alguém passar o mouse pelo cabeçalho e três painéis dispararem em sequência. Ajuste.

Uma ordem de migração que não dói

Mantenha as duas bibliotecas instaladas. Migre um componente por commit. Rode seus testes entre cada um. Nesta ordem:

  1. Componentes folha sem popup: Switch, Checkbox, Slider, Toggle, Progress. Em grande parte, trabalho de renomear.
  2. Tabs e Accordion. Corrija os valores em array e decida sobre activateOnFocus.
  3. Dialog e Alert Dialog. Aprenda a estrutura Backdrop e Popup e o padrão details.cancel() aqui, onde não há Positioner para confundir.
  4. Popover e Tooltip. Adicione o Positioner.
  5. Select, Combobox, Dropdown Menu, Context Menu. Valores anuláveis, closeOnClick e toda a árvore de popup. Faça estes por último, com o maior número de testes.

Substituir um componente pela versão em Base UI é um comando da CLI, e o seu código está no git, então revise o diff em vez de confiar nele:

npx shadcn@latest add dialog --overwrite

Depois git diff components/ui/dialog.tsx e leia cada linha antes de manter.

O que eu não faria

Eu não rodaria um codemod na árvore inteira e torceria. As mudanças que quebram o build são fáceis de encontrar. As silenciosas não são, e um codemod não consegue te dizer que a sua página de configurações agora precisa de duas teclas para trocar de aba.

Eu também não trocaria a camada de primitivas e redesenharia os componentes no mesmo PR. A graça do shadcn/ui é que o código é seu. Ser dono dele significa ler o diff.

Se você quer a migração feita por você

O HextaUI é um registry que você adiciona a um projeto shadcn/ui. Todo componente já está escrito sobre Base UI, mantém os nomes e as props do shadcn/ui e usa os mesmos tokens de tema, então npx shadcn@latest add https://hextaui.com/r/dialog.json --overwrite coloca um Dialog em Base UI em components/ui, ao lado dos seus outros arquivos. Os componentes são MIT e gratuitos. A página HextaUI vs shadcn/ui lista o que é diferente e quando o shadcn/ui puro é a escolha melhor.

FAQ

O Radix foi descontinuado? Não. O shadcn/ui publica todos os componentes para as duas bibliotecas, e o time ainda roda Radix em produção.

Posso misturar componentes Radix e Base UI em um mesmo projeto? Sim. Você carrega as duas dependências enquanto migra. Muitos projetos vão ficar misturados por bastante tempo, e tudo bem.

Preciso mudar meus tokens de tema? Não. --background, --primary, --radius e os demais continuam iguais. A camada de primitivas é independente do lado Tailwind.

O Base UI suporta React Server Components? As primitivas do Base UI são client components, assim como as do Radix. Mantenha a diretiva "use client" onde o shadcn/ui a coloca.

Onde está o changelog oficial? A entrada de julho de 2026 está em ui.shadcn.com/docs/changelog/2026-07-base-ui-default.