migrate-radix-to-base
Migrate React projects and shadcn components from Radix UI to Base UI, handling route objects, consumer props, and progressive wrapper migration.
Install
npx skills add https://github.com/shadcn-ui/ui --skill migrate-radix-to-baseRadix → Base UI props mapping: disclosure + toggle family
Scope: accordion, collapsible, tabs, toggle, toggle-group, toolbar.
Sources: radix-ui.com primitives docs + base-ui.com .md docs, cross-checked against installed @base-ui/react@1.6.0 .d.ts files (the published docs page for accordion lagged; types are authoritative here).
Conventions that apply to every component below:
asChild(boolean, defaultfalse) →render(ReactElement | (props: HTMLProps, state) => ReactElement). Signature changed: instead of a lone child element, pass the element torender; Base UI merges props onto it. Button-rendering parts additionally acceptnativeButton(defaulttrue), set it tofalsewhenrenderproduces a non-<button>element.- Base UI
classNameandstylealso accept a(state) => valuefunction form. - Radix
data-[state=...]value attributes become Base UI presence attributes (data-open,data-closed,data-pressed,data-active). - Base UI change callbacks all gained a second
eventDetailsargument ({ reason, event, cancel(), ... }). - Radix
dirprops are dropped everywhere; Base UI reads direction from the DOMdirattribute /DirectionProvider.
accordion
Part mapping: Root → Root, Item → Item, Header → Header, Trigger → Trigger, Content → Panel.
Accordion.Root → Accordion.Root
Accordion.Item → Accordion.Item
Accordion.Header → Accordion.Header
Accordion.Trigger → Accordion.Trigger
Accordion.Content → Accordion.Panel
Base UI only props worth knowing
Root.hiddenUntilFound/Panel.hiddenUntilFound(defaultfalse): useshidden="until-found"so browser find-in-page can expand panels; overrideskeepMounted. No Radix equivalent.Root.keepMounted: root-level version of the per-panel prop.Item.onOpenChange:(open: boolean, eventDetails: Accordion.Item.ChangeEventDetails) => void, per-item open callback. No Radix equivalent.Trigger.nativeButton(defaulttrue).className/stylestate-function forms on every part.
Data-attribute mapping
CSS var mapping
collapsible
Part mapping: Root → Root, Trigger → Trigger, Content → Panel.
Collapsible.Root → Collapsible.Root
Collapsible.Trigger → Collapsible.Trigger
Collapsible.Content → Collapsible.Panel
Base UI only props worth knowing
Panel.hiddenUntilFound(defaultfalse): find-in-page support viahidden="until-found"; overrideskeepMounted.Trigger.nativeButton(defaulttrue).className/stylestate-function forms.
Data-attribute mapping
CSS var mapping
tabs
Part mapping: Root → Root, List → List, Trigger → Tab, Content → Panel. Base UI adds an Indicator part with no Radix equivalent.
Tabs.Root → Tabs.Root
Tabs.List → Tabs.List
Tabs.Trigger → Tabs.Tab
Tabs.Content → Tabs.Panel
Base UI only props worth knowing
Tabs.Indicator: new part, a<span>that tracks the active tab for sliding-highlight UIs;renderBeforeHydration(defaultfalse) for SSR-flash avoidance. Exposes the--active-tab-*CSS vars below.List.activateOnFocus(see above).Tab.nativeButton, state-functionclassName/styleon all parts.
Data-attribute mapping
CSS var mapping
Radix Tabs exposes no CSS variables. Base UI only (on Indicator): --active-tab-left, --active-tab-right, --active-tab-top, --active-tab-bottom, --active-tab-width, --active-tab-height.
toggle
Part mapping: Toggle.Root → Toggle (single-part; Base UI export is directly callable, no .Root).
Toggle.Root → Toggle
Base UI only props worth knowing
value?: string: identifies the toggle inside a Base UIToggleGroup(this replaces RadixToggleGroup.Item'svalue, see toggle-group below).nativeButton(defaulttrue), state-functionclassName/style.
Data-attribute mapping
CSS var mapping
None on either side.
toggle-group
Part mapping: ToggleGroup.Root → ToggleGroup (callable single export), ToggleGroup.Item → Toggle (Base UI reuses the Toggle primitive as group items).
ToggleGroup.Root → ToggleGroup
ToggleGroup.Item → Toggle
Note: the item also gains the full standalone Toggle API (pressed, defaultPressed, onPressedChange with eventDetails) since it IS the Toggle primitive; inside a group the group value normally drives pressed state.
Base UI only props worth knowing
multiple(covered above) and the always-array value model.- Items are plain
Toggles, so per-itemonPressedChangeis available. - State-function
className/style.
Data-attribute mapping
CSS var mapping
None on either side.
toolbar
Part mapping: Root → Root, Button → Button, Link → Link, Separator → Separator. Toolbar.ToggleGroup/Toolbar.ToggleItem are DROPPED as dedicated parts: compose the standalone ToggleGroup with <Toolbar.Button render={<Toggle />} value="..."> as items (Base UI docs pattern). Base UI adds Group and Input parts with no Radix equivalent.
Toolbar.Root → Toolbar.Root
Toolbar.Button → Toolbar.Button
Toolbar.Link → Toolbar.Link
Toolbar.ToggleGroup → ToggleGroup (standalone, composed)
Toolbar.ToggleItem → Toolbar.Button render={}
Toolbar.Separator → Toolbar.Separator
Base UI only props worth knowing
Root.disabled: disables the entire toolbar (no Radix equivalent).Toolbar.Group(new part): groups related items, with a group-leveldisabled(defaultfalse).Toolbar.Input(new part):<input>wired into toolbar arrow-key navigation;defaultValue,disabled(defaultfalse),focusableWhenDisabled(defaulttrue).Button.disabled(defaultfalse) +Button.focusableWhenDisabled(defaulttrue): disabled buttons remain focusable for discoverability; setfocusableWhenDisabled={false}for Radix-like behavior.Button.nativeButton(defaulttrue), state-functionclassName/styleon all parts.
Data-attribute mapping
CSS var mapping
None on either side.