Redpanda UIRedpanda UI
General

Upgrading to Base UI

Migrating consumer apps to the Base UI–backed registry (major release).

Who this is for

Any project that installs components via shadcn add "https://redpanda-ui-registry.netlify.app/r/<component>" (or the @redpanda/... namespaced form). If you only use the docs site or the playground, no action is needed.

1. Install the new peer dependency

bun add @base-ui/react@^1.4.0
bun remove radix-ui   # only if nothing else uses Radix directly

npm / pnpm / yarn work the same way.

2. Re-run shadcn add

Re-install every component you consume so the latest Base UI–backed source is copied in:

bunx shadcn@latest add @redpanda/button @redpanda/dialog @redpanda/popover  # …and the rest

3. Update call sites

The compat shim is gone, so these Radix-era patterns must change in your own code.

A jscodeshift codemod automates the mechanical, 1:1 changes and prints a checklist of the context-dependent ones it leaves for you:

bunx jscodeshift -t https://redpanda-ui-registry.netlify.app/codemods/base-ui-migration.ts \
  --parser=tsx --extensions=tsx,ts ./src

Add -d -p for a dry run that prints the diff without writing.

Auto-appliedFlagged for manual review
asChildrender (skips the vaul Drawer)data-[state=open|closed] and any other residual data-[state=…] — context-dependent (data-open, data-popup-open, data-panel-open)
data-[state=checked|unchecked|on|active]data-[checked|unchecked|pressed|active]onOpenAutoFocusinitialFocus (Drawer is exempt); Accordion type/collapsible and ResizablePanelGroup direction
onSelectonClick on menu items (adds closeOnClick={false} when the handler called preventDefault)the now-redundant event.preventDefault() left inside a converted handler

The remaining sections describe each change so you can resolve the flagged items.

asChildrender

- <DialogTrigger asChild><Button>Open</Button></DialogTrigger>
+ <DialogTrigger render={<Button>Open</Button>} />

Affects every trigger / anchor / close (Dialog, AlertDialog, Popover, HoverCard, DropdownMenu, ContextMenu, Menubar, Collapsible, Tooltip) and the polymorphic parts (Badge, Item, Breadcrumb, ButtonGroupText, Button).

onSelectonClick (menu items)

Base UI's menu items fire onClick, not Radix's onSelect. Because onSelect is a valid DOM attribute it still type-checks, so this fails silently — the handler simply never runs.

- <DropdownMenuItem onSelect={() => onEdit(id)}>Edit</DropdownMenuItem>
+ <DropdownMenuItem onClick={() => onEdit(id)}>Edit</DropdownMenuItem>

When you used event.preventDefault() to keep the menu open (e.g. opening a dialog from a menu item), express that with closeOnClick={false} instead:

- <DropdownMenuItem onSelect={(e) => { e.preventDefault(); setOpen(true); }}>Delete</DropdownMenuItem>
+ <DropdownMenuItem closeOnClick={false} onClick={() => setOpen(true)}>Delete</DropdownMenuItem>

Affects DropdownMenu, ContextMenu, and Menubar Item / CheckboxItem / RadioItem. The registry now types onSelect as never on these, so passing it is a compile error rather than a silent no-op.

data-[state=…] → native Base UI data attributes

- data-[state=open]:bg-accent
+ data-[popup-open]:bg-accent

Remap your Tailwind selectors:

Radix (old)Base UI (new)
data-[state=open] / data-[state=closed]data-open / data-closed (menu & popover triggers use data-popup-open)
data-[state=checked] / data-[state=unchecked]data-checked / data-unchecked
toggle / toggle-group data-[state=on]data-pressed
accordion data-[state=open]data-panel-open
disabled statedata-disabled

Callback signatures

Base UI passes event details as a second argument:

- onValueChange={(value) => …}
+ onValueChange={(value, eventDetails) => …}

Applies to onValueChange / onOpenChange / onCheckedChange / onPressedChange.

onOpenAutoFocusinitialFocus

The overlay primitives replaced Radix's onOpenAutoFocus with Base UI's initialFocus. (The vaul Drawer keeps onOpenAutoFocus.)

Accordion

- <Accordion type="single" collapsible>            {/* single + collapsible is now the default */}
+ <Accordion>
- <Accordion type="multiple" value={["item-1"]}>   {/* array-valued */}
+ <Accordion multiple value={["item-1"]}>

ToggleGroup

Like Accordion, ToggleGroup dropped Radix's type discriminator. value/defaultValue are now always string[], even for single-select, and onValueChange receives a string[]:

- <ToggleGroup type="single" value="left">              {/* single is the default */}
+ <ToggleGroup value={["left"]}>
- <ToggleGroup type="multiple" value={["bold","italic"]}>
+ <ToggleGroup multiple value={["bold","italic"]}>

Single-select also moved from role="radiogroup"/role="radio" to native toggle semantics (aria-pressed).

Variant / size renames

These props were renamed to match shadcn — TypeScript flags the size changes, but variant string literals fail silently (the component falls back to its default), so audit these by hand:

ComponentOldNew
togglebase variant="ghost"variant="default"
togglesize="md"size="default"
alert-dialogAlertDialogCancel default ghostoutline

Dependency upgrades

  • chartrecharts v2 → v3. recharts v3 removed component defaultProps, so any composition inside ChartContainer that relied on default axis/series props must now set them explicitly. v3 also pulls @reduxjs/toolkit / react-redux into the graph — a bundle delta worth measuring.
  • resizablereact-resizable-panels v3 → v4. The ResizablePanelGroup direction prop is now orientation, and panel sizes are percentage strings (defaultSize="50%", not defaultSize={50}).

Reference

The breaking changes for this release are documented above and in the release changeset (.changeset/base-ui-shadcn-realignment.md). Subsequent per-component changes are tracked in the changelog.

Built by malinskibeniamin. The source code is available on GitHub.

On this page