← Blog

shadcn/ui ist auf Base UI umgestiegen. Das ändert sich wirklich in deinem Code

shadcn/ui nutzt jetzt standardmäßig Base UI statt Radix. Jede API-Änderung, auf die du stößt, der Ausweg -b radix und eine Migrationsreihenfolge, die nicht wehtut.

Preet Suthar · · 5 Min. Lesezeit

  • React
  • shadcn/ui
  • Base UI
  • Radix

Am 3. Juli 2026 hat npx shadcn init angefangen, Base UI statt Radix zu wählen. Wenn du nur die Schlagzeilen gelesen hast, denkst du vermutlich eins von zwei Dingen: dass Radix tot ist oder dass sich nichts geändert hat. Beides stimmt nicht.

Ich pflege HextaUI, eine auf Base UI gebaute shadcn/ui-Registry, und habe deshalb den Großteil dieses Jahres in beiden Primitive-Schichten verbracht. Das ist der Leitfaden, den ich mir im Juli gewünscht hätte: was sich geändert hat, was nicht, jeder API-Unterschied, auf den du stößt, und die Reihenfolge für die Migration, falls du dich dafür entscheidest.

Was sich geändert hat, in einem Absatz

Neue shadcn/ui-Projekte bekommen Base-UI-Primitives. Radix wird weiterhin voll unterstützt. Das shadcn-Team hat gesagt, dass jedes Update und jede neue Komponente für beide Bibliotheken erscheint, es sei denn, eine Komponente existiert nur in Base UI. Bestehende Projekte bleiben unberührt. Wenn du Radix in einem frischen Projekt willst, übergibst du ein Flag:

npx shadcn@latest init -b radix

Wenn du CI oder Skripte hast, die init nicht-interaktiv aufrufen und Radix erwarten, füge dieses Flag jetzt hinzu, bevor dich ein frischer Checkout überrascht.

Warum sich der Standard verschoben hat

Zwei Gründe wurden genannt. Base UI hatte ein stabiles 1.6.0 mit über sechs Millionen wöchentlichen Downloads erreicht, und über shadcn/create erstellte Projekte wählten bereits etwa zwei zu eins Base UI statt Radix. Der Standard folgte dem, was die Leute wählten.

Base UI wird von den Leuten hinter Radix, Material UI und Floating UI gebaut, und die Herkunft merkt man. Es deckt Primitives ab, die Radix nie geliefert hat, etwa Combobox, Autocomplete, Number Field, Toast und Preview Card. Stand September 2026 steht es bei 1.8.0.

Solltest du eine bestehende App migrieren?

Wahrscheinlich nicht in diesem Quartal, und nicht alles auf einmal.

Bleib bei Radix, wenn die App stabil ist, dich kein fehlendes Primitive blockiert und niemand im Team eine konkrete Beschwerde hat. Nichts, worauf du dich verlässt, verschwindet.

Migriere, wenn du Combobox oder Number Field brauchst, ohne zu einer zweiten Bibliothek zu greifen, wenn du die Komponenten ohnehin gerade für Tailwind v4 neu schreibst, oder wenn du auf der Schicht sein willst, auf die neue shadcn/ui-Komponenten zuerst zielen.

So oder so, lies den nächsten Abschnitt, bevor du dich entscheidest, denn die Unterschiede sind nicht kosmetisch.

Jeder API-Unterschied, auf den du wirklich stößt

Ich habe sie danach gruppiert, wie sie fehlschlagen. Die erste Gruppe bricht deinen Build. Die zweite Gruppe kompiliert problemlos und ändert das Verhalten stillschweigend, was schlimmer ist.

Änderungen, die den Build brechen

asChild wird zu render. Das ist die Änderung, auf die alle zuerst stoßen.

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

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

Die render-Prop nimmt ein Element oder eine Funktion. Die Funktionsform gibt dir den Zustand, nützlich etwa für einen Trigger, der sein Icon wechselt, wenn er offen ist.

Verankerte Popups bekommen einen Positioner. Menu, Select, Popover und Tooltip gehen von Portal > Content zu Portal > Positioner > Popup. Dialog hat keinen Positioner; er nutzt Backdrop und Popup.

Overlay heißt jetzt Backdrop. Umbenennen und weiter.

Select-Werte können null sein. Ein kontrolliertes Select hält Value | null, nicht string. Aus deinem useState<string>("") wird useState<string | null>(null), und jede Stelle, die den Wert als String gelesen hat, braucht eine Absicherung.

Accordion- und Toggle-Group-Werte sind immer Arrays. Selbst im Single-Modus wird aus value="a" ein value={["a"]}. Tabs behalten einen einzelnen Wert.

onOpenChange hat ein zweites Argument. Bei Radix rufst du event.preventDefault() auf, um ein Schließen zu verhindern. Base UI übergibt (open, details), und du rufst details.cancel() auf. Das Feld details.reason sagt dir, ob es die Escape-Taste, ein Klick außerhalb oder etwas anderes war, praktisch für „ungespeicherte Änderungen“-Dialoge.

Selektoren für den Offen-Zustand ändern sich. Aus data-[state=open]:-Varianten wird data-open:. Das versteckt sich in deinen className-Strings und in cva-Varianten, also greppe nach data-[state=, bevor du die Migration für fertig erklärst.

Stille Verhaltensänderungen

Tabs aktivieren standardmäßig manuell. In Radix bewegen die Pfeiltasten den Fokus und aktivieren den Tab. In Base UI bewegen die Pfeiltasten den Fokus, und du drückst Enter oder die Leertaste zum Aktivieren. Wenn deine Tests oder Nutzer das Radix-Verhalten erwarten, setze activateOnFocus auf der Liste.

Checkbox- und Radio-Menüeinträge lassen das Menü offen. closeOnClick ist für CheckboxItem und RadioItem standardmäßig false. Normale Einträge schließen weiterhin. Das ist wohl der bessere Standard, aber eine Änderung, die deine Nutzer bemerken werden.

Navigationsmenüs öffnen schneller. Base UIs Hover-delay ist standardmäßig 50ms. Radix' delayDuration war standardmäßig 200ms. Schnelle Menüs fühlen sich großartig an, bis jemand mit der Maus über den Header fährt und drei Panels hintereinander aufgehen. Stell es ein.

Eine Migrationsreihenfolge, die nicht wehtut

Lass beide Bibliotheken installiert. Migriere eine Komponente pro Commit. Lass dazwischen jeweils deine Tests laufen. In dieser Reihenfolge:

  1. Blattkomponenten ohne Popup: Switch, Checkbox, Slider, Toggle, Progress. Größtenteils Umbenennungsarbeit.
  2. Tabs und Accordion. Korrigiere die Array-Werte und entscheide über activateOnFocus.
  3. Dialog und Alert Dialog. Lerne hier die Struktur aus Backdrop und Popup und das details.cancel()-Pattern, wo kein Positioner die Dinge verkompliziert.
  4. Popover und Tooltip. Füge den Positioner hinzu.
  5. Select, Combobox, Dropdown Menu, Context Menu. Nullbare Werte, closeOnClick und der ganze Popup-Baum. Mach diese zuletzt, mit den meisten Tests.

Eine Komponente durch die Base-UI-Version zu ersetzen ist ein CLI-Befehl, und dein Quellcode liegt in Git, also prüfe den Diff, statt ihm zu vertrauen:

npx shadcn@latest add dialog --overwrite

Dann git diff components/ui/dialog.tsx und jede Zeile lesen, bevor du es behältst.

Was ich nicht tun würde

Ich würde keinen Codemod über den ganzen Baum laufen lassen und hoffen. Die Änderungen, die den Build brechen, sind leicht zu finden. Die stillen nicht, und ein Codemod kann dir nicht sagen, dass deine Einstellungsseite jetzt zwei Tastendrücke braucht, um Tabs zu wechseln.

Ich würde auch nicht im selben PR die Primitive-Schicht wechseln und die Komponenten neu gestalten. Der Sinn von shadcn/ui ist, dass dir der Code gehört. Ihn zu besitzen heißt, den Diff zu lesen.

Wenn du die Migration erledigt haben willst

HextaUI ist eine Registry, die du zu einem shadcn/ui-Projekt hinzufügst. Jede Komponente ist bereits auf Base UI geschrieben, behält die shadcn/ui-Namen und -Props und nutzt dieselben Theme-Tokens, sodass npx shadcn@latest add https://hextaui.com/r/dialog.json --overwrite einen Base-UI-Dialog in components/ui neben deine anderen Dateien legt. Die Komponenten sind MIT und kostenlos. Die Seite HextaUI vs shadcn/ui listet auf, was anders ist und wann reines shadcn/ui die bessere Wahl ist.

Häufige Fragen

Ist Radix veraltet? Nein. shadcn/ui liefert jede Komponente für beide Bibliotheken, und das Team betreibt Radix weiterhin in Produktion.

Kann ich Radix- und Base-UI-Komponenten in einem Projekt mischen? Ja. Während der Migration trägst du beide Abhängigkeiten. Viele Projekte bleiben lange gemischt, und das ist in Ordnung.

Muss ich meine Theme-Tokens ändern? Nein. --background, --primary, --radius und der Rest sind unverändert. Die Primitive-Schicht ist unabhängig von der Tailwind-Seite.

Unterstützt Base UI React Server Components? Base-UI-Primitives sind Client-Komponenten, genau wie Radix. Lass die "use client"-Direktive dort, wo shadcn/ui sie hinsetzt.

Wo ist das offizielle Changelog? Der Eintrag vom Juli 2026 steht unter ui.shadcn.com/docs/changelog/2026-07-base-ui-default.