Blog

UI SDK vs Base UI vs Radix: qual biblioteca headless usar para componentes estilo shadcn?

UI SDK da Vercel, Base UI e Radix comparados para componentes estilo shadcn: modelo, frameworks, cobertura, estilo, animação, beta, licença e agentes.

Preet Suthar · · 9 min de leitura

  • React
  • UI SDK
  • Base UI
  • Radix

Até esta semana, escolher a camada de primitivas sob uma biblioteca de componentes no estilo shadcn/ui significava escolher entre duas opções: Radix e Base UI. Em 12 de novembro de 2026, a Vercel Labs publicou uma terceira, o UI SDK, como 0.1.0-beta.1. Ele é headless como os outros dois, mas é construído em torno de outra ideia e traz algo que nenhum deles tem: uma ponte que deixa agentes de IA lerem e operarem os componentes.

Um aviso antes da comparação. Os componentes da HextaUI são construídos sobre o Base UI hoje, e estou adicionando uma versão separada em UI SDK ao lado. Tenho motivos para gostar dos dois, então tentei ser justo, e toda afirmação abaixo vem da documentação das bibliotecas, dos metadados delas no npm ou de um app de teste em que construí o mesmo componente três vezes.

Se você quer a migração do Radix para o Base UI em detalhes, com cada mudança de API no código do shadcn/ui, leia antes O shadcn/ui migrou para o Base UI. Este post não repete aquilo. Ele adiciona o UI SDK ao quadro e pergunta por qual começar.

A resposta curta

  • Escolha o Radix se você tem uma base de código Radix funcionando, seu time a conhece e você não precisa de um componente que falta nele. É estável, só para React e MIT.
  • Escolha o Base UI para um novo design system em React que você quer lançar com APIs estáveis neste trimestre. É o padrão em novos projetos shadcn/ui, cobre mais componentes que o Radix e usa o modelo de atributos para o qual a maior parte do ecossistema está migrando.
  • Escolha o UI SDK se você precisa de mais do que React, quer componentes como Color Picker, Date Picker ou Tree sem uma segunda biblioteca, ou quer que agentes de IA operem sua UI por meio de ferramentas estruturadas. Aceite que é um beta.

O resto do post são as evidências.

As três de relance

RadixBase UIUI SDK
Pacoteradix-ui 1.8.1@base-ui/react 1.9.0@ui-sdk/react 0.1.0-beta.1
LicençaMITMITApache-2.0
StatusEstávelEstávelBeta
FrameworksReactReactReact, Vue, Svelte, Solid, Web Components
Estado abertodata-state="open"data-opendata-open
Entrada e saídaKeyframes em data-statedata-starting-style, data-ending-styledata-starting-style, data-ending-style
data-slot nas partesNãoNãoSim
Partes do popupPortal, ContentPortal, Positioner, PopupConteúdo na top layer
Ponte para agentesNãoNãoSim, com WebMCP
Radix, Base UI e UI SDK de relance, novembro de 2026

As três são sem estilo e cuidam de foco, teclado e ARIA para você. As versões comparadas são as mais recentes no npm em 13 de novembro de 2026: radix-ui 1.8.1 (publicada em 12 de outubro de 2026), @base-ui/react 1.9.0 (9 de outubro de 2026) e @ui-sdk/react 0.1.0-beta.1 (12 de novembro de 2026).

Três modelos diferentes

O Radix publica uma primitiva por componente. Um pacote Dialog te dá Root, Trigger, Portal, Overlay, Content, Title, Description e Close, e essa é a unidade com que você trabalha. Os comportamentos internos, como prender o foco e fechar, são internos. O pacote radix-ui reúne todos.

O Base UI tem o mesmo formato: um componente, várias partes. Ele adiciona mais utilitários públicos em volta delas, como useRender e mergeProps, para você escrever suas próprias partes que se comportam como as nativas. É só para React, feito por pessoas que trabalharam no Radix, no Material UI e no Floating UI.

O UI SDK divide as camadas de outro jeito. @ui-sdk/core é uma biblioteca TypeScript de comportamentos: roving focus, typeahead, popups, arrastar e soltar, anúncios e muitos outros. Uma primitiva combina comportamentos com estado e slots nomeados por meio de definePrimitive. Os componentes que você importa de @ui-sdk/react são primitivas renderizadas pelo adaptador React, e as mesmas primitivas são renderizadas em Vue, Svelte, Solid e Web Components pelos adaptadores deles. A documentação vende isso pensando em agentes: dê a um agente comportamentos testados e ele consegue construir um componente que a biblioteca não traz, como um seletor de ícones ou um quadro kanban.

A diferença prática: no Radix e no Base UI, um componente de que você precisa e que eles não têm é problema seu, do zero. No UI SDK, você o constrói com os mesmos comportamentos que os componentes nativos usam, e ele pode entrar na ponte para agentes.

O mesmo popover, três anatomias

Construí um popover três vezes em um app Vite com as mesmas classes do Tailwind. Aqui estão as árvores, reduzidas às partes que diferem:

import { Popover as BasePopover } from "@base-ui/react/popover"
import {
  Popover,
  PopoverContent,
  PopoverTitle,
  PopoverTrigger,
} from "@ui-sdk/react/popover"
import { Popover as RadixPopover } from "radix-ui"

function RadixVersion() {
  return (
    <RadixPopover.Root>
      <RadixPopover.Trigger className={trigger}>Radix</RadixPopover.Trigger>
      <RadixPopover.Portal>
        <RadixPopover.Content
          align="start"
          sideOffset={8}
          className={`${panel} origin-(--radix-popover-content-transform-origin) data-[state=open]:animate-[popover-in_150ms_ease-out] data-[state=closed]:animate-[popover-out_100ms_ease-in]`}
        >
          <p className="font-medium">Share project</p>
        </RadixPopover.Content>
      </RadixPopover.Portal>
    </RadixPopover.Root>
  )
}

function BaseUIVersion() {
  return (
    <BasePopover.Root>
      <BasePopover.Trigger className={trigger}>Base UI</BasePopover.Trigger>
      <BasePopover.Portal>
        <BasePopover.Positioner align="start" sideOffset={8}>
          <BasePopover.Popup
            className={`${panel} origin-(--transform-origin) transition-[opacity,scale] duration-150 data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0`}
          >
            <BasePopover.Title className="font-medium">
              Share project
            </BasePopover.Title>
          </BasePopover.Popup>
        </BasePopover.Positioner>
      </BasePopover.Portal>
    </BasePopover.Root>
  )
}

function UISDKVersion() {
  return (
    <Popover>
      <PopoverTrigger className={trigger}>UI SDK</PopoverTrigger>
      <PopoverContent
        align="start"
        sideOffset={8}
        className={`${panel} origin-(--transform-origin) transition-[opacity,scale] duration-150 data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0`}
      >
        <PopoverTitle className="font-medium">Share project</PopoverTitle>
      </PopoverContent>
    </Popover>
  )
}

trigger e panel são strings de classe compartilhadas, e a versão Radix usa duas pequenas regras @keyframes para a entrada e a saída. O resultado é idêntico nos três:

Três popovers abertos empilhados, sob triggers chamados Radix, Base UI e UI SDK, cada um com Share project
O mesmo popover feito com Radix, Base UI e UI SDK, capturado do app de teste. Os pixels batem. O DOM e o código, não.

Radix

  • Popover.Root
  • Popover.Trigger
  • Popover.Portal
  • Popover.Content

Base UI

  • Popover.Root
  • Popover.Trigger
  • Popover.Portal
  • Popover.Positioner
  • Popover.Popup

UI SDK

  • Popover
  • PopoverTrigger
  • PopoverContentRenderizado no lugar, na top layer

Uma parte que existe para mover ou posicionar o popup

O mesmo popover, três árvores de partes

O que muda é onde o conteúdo fica. No meu teste, Radix e Base UI moveram o conteúdo aberto para o fim do <body> pelo Portal. O UI SDK o deixou dentro do próprio componente, com popover="manual", e o navegador o promoveu para a top layer. Isso tem efeitos em cadeia:

  • Sem brigas de empilhamento. O conteúdo na top layer fica acima da página, seja qual for o z-index de um cabeçalho sticky ou de um pai com transform.
  • Eventos sobem pela sua árvore. A documentação do UI SDK avisa que um handler de clique ou um link em volta de um popup também recebe os cliques de dentro dele. Mantenha popups fora de cards clicáveis.
  • Menos partes wrapper. Props de posicionamento como side, align e sideOffset vão no conteúdo, não em um Positioner.

Radix e Base UI lidam bem com portals há anos, então o primeiro ponto é menos uma correção e mais uma simplificação. O segundo ponto é um comportamento novo que você precisa conhecer.

Ganchos de estilização

As três colocam o estado em atributos data, mas os nomes mudam, e só uma nomeia todas as partes.

HookRadixBase UIUI SDK
Estado abertodata-state="open"data-opendata-open
Nome da partenenhumnenhumdata-slot em toda parte
Transform origin--radix-popover-content-transform-origin--transform-origin--transform-origin
Largura da âncora--radix-popover-trigger-width--anchor-width--anchor-width
Estado em `className`nãofunção do estadofunção do estado

A linha do data-slot importa mais do que parece. O shadcn/ui adiciona seus próprios atributos data-slot nos arquivos wrapper, porque Radix e Base UI não emitem um. No meu app de teste, o popup do Base UI tinha data-open, data-side e data-align, e nenhum data-slot. O UI SDK o define ele mesmo em cada parte, como data-slot="popover-content", e a documentação o trata como um atributo gerenciado que suas props não conseguem sobrescrever. Isso dá ao CSS global e aos agentes um nome estável para cada parte.

Se você estiliza com Tailwind, ir do Base UI para o UI SDK é basicamente apagar wrappers, porque os nomes de atributos dos popups são compartilhados. Sair do Radix significa trocar as variantes data-[state=open]: e as variáveis com prefixo do Radix. O tutorial de estilização do UI SDK passa por quatro componentes com tokens estilo shadcn.

Animação

O Radix anima montagem e desmontagem com animações CSS em data-state. A documentação dele diz que suspende a desmontagem enquanto a animação de saída roda. Para bibliotecas de animação em JavaScript, muitas partes aceitam forceMount, para que a biblioteca decida quando os filhos são montados.

O Base UI adiciona data-starting-style e data-ending-style, então transições CSS funcionam nos dois sentidos, não só keyframes. O popup continua montado até a animação terminar e depois é removido, ou ocultado se o Portal tiver keepMounted. O guia de animação dele cobre o Motion e diz que detecta animações do Motion que incluem opacity.

O UI SDK usa os mesmos dois atributos com o mesmo significado. O conteúdo continua montado até a transição ou animação de saída terminar, keepMounted mantém o conteúdo fechado na página, e onOpenChangeComplete roda quando a abertura ou o fechamento termina. A documentação do UI SDK ainda não tem um guia para o Motion, além de uma nota para usar keepMounted quando uma biblioteca de animação precisa que o conteúdo fechado continue no DOM.

Para animação só com CSS, Base UI e UI SDK são praticamente iguais. Para interfaces com muito Motion, o Base UI tem padrões documentados hoje e o UI SDK não.

Cobertura de componentes

Contar componentes entre bibliotecas é impreciso, porque cada uma divide e nomeia as coisas de um jeito. Como referência:

  • Radix: 30 pacotes de primitivas no radix-ui 1.8.1, de Accordion a Tooltip, mais utilitários como Slot, Portal e Visually Hidden.
  • Base UI: 37 componentes no índice da documentação, incluindo Combobox, Autocomplete, Number Field, OTP Field, Drawer, Toast e Preview Card, que o Radix não tem.
  • UI SDK: 55 componentes e dois hooks na documentação que vem com a 0.1.0-beta.1.

A maioria dos componentes do Base UI tem um equivalente no UI SDK, às vezes com outro nome: Number Field é Number Input, Preview Card é Hover Card, OTP Field é OTP Input. O UI SDK também traz componentes que nenhum dos outros dois tem, incluindo Calendar, Date Picker, Date Input, Time Input, Color Picker, Rating, Tree, Command, Carousel, Resizable, Image Cropper, File Input, Token Input, Trigger Menu, Timeline e Waterfall.

Quantidade não é qualidade. Os componentes do Radix e do Base UI têm anos de uso em produção. As páginas de componentes do UI SDK listam seus comportamentos testados linha por linha, o que é um bom sinal, mas um dia de vida pública não equivale a anos de relatos de bugs.

A ponte para agentes

Esta é a parte que só o UI SDK tem. createAgent() de @ui-sdk/react/agent se conecta a todos os componentes do UI SDK na página. snapshot() os lista com seus labels, estado e ações disponíveis. act() chama essas ações pela API de cada componente, e seus callbacks de mudança veem details.source === "agent". registerTools() oferece as mesmas ferramentas a um agente embutido no navegador via WebMCP.

Radix e Base UI não têm nada parecido embutido. Um agente as controla do jeito que controla qualquer página web: por screenshots, pelo DOM ou pela árvore de acessibilidade, com ferramentas de navegador que clicam e digitam.

A documentação da Vercel inclui um benchmark dessa diferença em uma tarefa de configurações de projeto. Pelos números deles, com Sonnet via Claude Code e medianas de três execuções, a ponte para agentes reduziu as chamadas de ferramenta de 16 para 5 e o custo de $0.073 para $0.032. São números da Vercel para uma tarefa, não meus, mas a direção faz sentido: ler uma lista de componentes com seu estado é mais barato do que ler uma página. Montei uma demo funcional da ponte, com permissões e um componente customizado, em Torne seu app React operável por agentes de IA.

Se agentes usando sua UI não estão no seu roadmap, esta seção não deveria decidir nada. Se estão, nada mais nesta comparação chega perto.

Maturidade e licença

Radix e Base UI são bibliotecas 1.x estáveis. As APIs mudam seguindo semver, e o shadcn/ui publica todos os componentes para as duas.

O UI SDK diz no README e na documentação que está em beta e que o framework, as APIs, a documentação e o comportamento podem mudar antes da disponibilidade geral. O beta é coberto pelo contrato público de beta da Vercel. Algumas APIs também são marcadas como experimentais, incluindo a chave agent em primitivas customizadas e os helpers de histórico e desfazer. O repositório no GitHub, vercel-labs/ui, foi criado em outubro de 2026, e a única entrada do changelog é "Introducing UI SDK", em 12 de novembro de 2026.

As licenças também diferem. Radix e Base UI são MIT. O UI SDK é Apache-2.0, que também é permissiva e adiciona uma concessão explícita de patentes. Se você o redistribuir, a Apache-2.0 pede que você mantenha a licença e os avisos, então confira com quem cuida de licenças na sua empresa se isso importar.

Frameworks

Radix e Base UI são bibliotecas React. O UI SDK publica @ui-sdk/react, @ui-sdk/vue, @ui-sdk/svelte, @ui-sdk/solid e @ui-sdk/web na mesma versão, todos sobre um único core. Se sua empresa tem um app React e um app Vue, ou quer uma build em Web Components para um CMS, o UI SDK é o único dos três que cobre todos com um só conjunto de comportamentos e nomes de atributos.

O pacote React do UI SDK exige React 19 e React DOM 19 ou mais recente, com @types/react 19.2.10 ou mais recente. Se você ainda está no React 18, isso decide por enquanto.

Quando escolher cada um

Fique no Radix quando o app funciona, o time o conhece e nada está te travando. Nada aqui obriga uma mudança. Se for mudar, vá primeiro para o Base UI: é o passo menor, e o modelo de atributos que você aprende vale também no UI SDK.

Comece no Base UI quando estiver construindo uma biblioteca de componentes React ou uma UI de produto para lançar agora. É estável, é o padrão do shadcn/ui, e seus padrões de popups, formulários e animação são bem documentados.

Comece no UI SDK quando uma destas for verdade:

  • você precisa que vários frameworks compartilhem um modelo de componentes;
  • você precisa dos componentes extras e, de outro jeito, adicionaria bibliotecas para datas, cores ou árvores;
  • agentes operando sua UI é um objetivo de produto, e você quer permissões e origens de mudança já embutidas;
  • você está prototipando e consegue absorver breaking changes durante o beta.

Misturar é possível. Componentes do UI SDK não precisam de provider, então um app em Base UI pode usar um Color Picker do UI SDK em uma tela. Você carrega duas dependências e dois conjuntos de nomes de atributos, então limite isso aos componentes que você não encontra em outro lugar.

O que a HextaUI está fazendo

Os componentes da HextaUI continuam no Base UI. Essa é a versão que está no site hoje, e ela não vai sumir. Também estou construindo uma versão separada em UI SDK, com o mesmo visual e os mesmos tokens de tema, para projetos que querem a ponte para agentes ou os componentes extras. Ela ainda não foi lançada. Quando for, você vai escolher uma versão, não migrar.