Skip to content

Migrate shadcn-admin-kit to Base UI#142

Open
WiXSL wants to merge 20 commits intomainfrom
test-base-ui-no-compat
Open

Migrate shadcn-admin-kit to Base UI#142
WiXSL wants to merge 20 commits intomainfrom
test-base-ui-no-compat

Conversation

@WiXSL
Copy link
Contributor

@WiXSL WiXSL commented Mar 11, 2026

Ref: #104

Reinstall shadcn/ui with Base UI instead of Radix-ui

  • Migrated the project from Radix UI to Base UI
  • Updated the shadcn to version 4 and regenerated the core UI wrappers
  • Reworked admin and website components to use Base UI patterns directly
  • Removed direct @radix-ui/* dependencies from the repo
  • Fixed migration regressions in menus, popovers, tooltips, forms, columns, and rich text components

How to Test

  • pnpm dev and test the main demo locally, including menus, popovers, columns, forms, and the rich text input demo route.
  • pnpm website:dev and check the website locally, especially header navigation, dropdowns, and tooltips.
  • pnpm storybook and review the updated component stories, especially Base UI wrappers and the rich text input.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates shadcn-admin-kit from Radix UI to Base UI (aligned with shadcn v4), regenerating the UI wrappers and updating admin/website components to the new headless patterns while removing Radix dependencies.

Changes:

  • Replaced Radix-based UI primitives (tooltip, dropdown/menu, dialogs, navigation menu, etc.) with Base UI equivalents across src/ and website/.
  • Updated styling/theme inputs (Tailwind imports, Inter variable font, radius tokens) and regenerated shadcn wrappers.
  • Updated registry/build tooling and metadata for shadcn v4.

Reviewed changes

Copilot reviewed 84 out of 85 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
website/src/components/ui/tooltip.tsx Tooltip wrapper migrated to Base UI.
website/src/components/ui/navigation-menu.tsx Navigation menu wrapper migrated to Base UI patterns.
website/src/components/ui/dropdown-menu.tsx Dropdown menu wrapper migrated to Base UI Menu.
website/src/components/ui/button.tsx Button wrapper migrated to Base UI Button.
website/src/components/Users.tsx Updated link buttons to Base UI render pattern.
website/src/components/Pricing.tsx Updated CTA button to Base UI render pattern.
website/src/components/HotspotSvg.tsx Updated tooltip trigger usage to Base UI render pattern.
website/src/components/Hero.tsx Updated hero CTA buttons to Base UI render pattern.
website/src/components/Header.tsx Updated header menus/buttons to Base UI render pattern.
website/src/components/CallToAction.tsx Updated CTA button to Base UI render pattern.
website/src/components/Backends.tsx Updated CTA button to Base UI render pattern.
website/src/components/AdvancedCapabilities.tsx Updated CTA button to Base UI render pattern.
src/lib/utils.ts Formatting update (semicolons removed).
src/index.css Added shadcn v4 Tailwind import + Inter font + radius tokens.
src/hooks/use-mobile.ts Formatting update (semicolons removed).
src/components/ui/tooltip.tsx Tooltip wrapper migrated to Base UI Tooltip.
src/components/ui/toggle.tsx Toggle wrapper migrated to Base UI Toggle.
src/components/ui/toggle-group.tsx ToggleGroup wrapper migrated to Base UI ToggleGroup.
src/components/ui/textarea.tsx Textarea styles adjusted.
src/components/ui/tabs.tsx Tabs wrapper migrated to Base UI Tabs + added variants.
src/components/ui/table.tsx Table marked client + minor class adjustments.
src/components/ui/switch.tsx Switch wrapper migrated to Base UI Switch + size support.
src/components/ui/sonner.tsx Toaster marked client + icons + styling options.
src/components/ui/skeleton.tsx Skeleton styling adjusted.
src/components/ui/sidebar.tsx Sidebar reworked to Base UI render/mergeProps patterns.
src/components/ui/sheet.tsx Sheet migrated from Radix Dialog to Base UI Dialog.
src/components/ui/separator.tsx Separator migrated to Base UI Separator with decorative handling.
src/components/ui/select.tsx Select migrated to Base UI Select with positioner/popup structure.
src/components/ui/radio-group.tsx RadioGroup migrated to Base UI Radio/RadioGroup.
src/components/ui/popover.tsx Popover migrated to Base UI Popover + new header/title/description helpers.
src/components/ui/pagination.tsx Pagination links switched to Button + render anchor pattern.
src/components/ui/navigation-menu.tsx Navigation menu wrapper migrated to Base UI patterns.
src/components/ui/label.tsx Label simplified to native <label>.
src/components/ui/input.tsx Input migrated to Base UI Input.
src/components/ui/input-group.tsx New InputGroup component added.
src/components/ui/dropdown-menu.tsx Dropdown menu migrated to Base UI Menu with submenu/indicators.
src/components/ui/drawer.tsx Drawer styles adjusted (vaul-based).
src/components/ui/dialog.tsx Dialog migrated to Base UI Dialog + close-button rendering.
src/components/ui/command.tsx Command palette styling and input rebuilt using InputGroup.
src/components/ui/checkbox.tsx Checkbox migrated to Base UI Checkbox.
src/components/ui/card.tsx Card styles/structure updated + size support.
src/components/ui/button.tsx Button migrated to Base UI Button + variant/size updates.
src/components/ui/breadcrumb.tsx Breadcrumb migrated to Base UI render utilities (useRender/mergeProps).
src/components/ui/badge.tsx Badge migrated to Base UI render utilities (useRender/mergeProps).
src/components/ui/avatar.tsx Avatar migrated to Base UI Avatar + group/badge additions.
src/components/ui/alert.tsx Alert styles updated + added AlertAction slot.
src/components/ui/accordion.tsx Accordion migrated to Base UI Accordion.
src/components/rich-text-input/minimal-tiptap/extensions/image/components/image-view-block.tsx Replaced Radix icons with lucide icons.
src/components/rich-text-input/minimal-tiptap/extensions/image/components/image-actions.tsx Updated menu/tooltip triggers to Base UI render pattern + lucide icons.
src/components/rich-text-input/minimal-tiptap/components/toolbar-section.tsx Updated dropdown trigger to Base UI render pattern + lucide icon.
src/components/rich-text-input/minimal-tiptap/components/toolbar-button.tsx Updated tooltip trigger typing/usage to Base UI render pattern.
src/components/rich-text-input/minimal-tiptap/components/section/two.tsx Replaced Radix icons with lucide icons.
src/components/rich-text-input/minimal-tiptap/components/section/three.tsx Updated tooltip/popover triggers + toggle group usage for Base UI.
src/components/rich-text-input/minimal-tiptap/components/section/one.tsx Updated dropdown trigger + icons for Base UI.
src/components/rich-text-input/minimal-tiptap/components/section/four.tsx Updated icons for Base UI.
src/components/rich-text-input/minimal-tiptap/components/section/five.tsx Updated icons for Base UI.
src/components/rich-text-input/minimal-tiptap/components/link/link-popover-block.tsx Replaced Radix icons with lucide icons.
src/components/rich-text-input/minimal-tiptap/components/link/link-edit-popover.tsx Updated popover trigger to Base UI render pattern + lucide icon.
src/components/rich-text-input/minimal-tiptap/components/image/image-edit-dialog.tsx Updated dialog trigger to Base UI render pattern + lucide icon.
src/components/admin/user-menu.tsx Updated dropdown trigger/content patterns for Base UI Menu.
src/components/admin/theme-mode-toggle.tsx Updated dropdown trigger to Base UI render pattern.
src/components/admin/sort-button.tsx Updated nested dropdown/tooltip trigger composition to Base UI render pattern.
src/components/admin/simple-form-iterator.tsx Updated tooltip triggers to Base UI render pattern.
src/components/admin/radio-button-group-input.tsx Adjusted prop typing exclusions.
src/components/admin/locales-menu-button.tsx Updated dropdown trigger to Base UI render pattern.
src/components/admin/icon-button-with-tooltip.tsx Updated tooltip trigger to Base UI render pattern.
src/components/admin/form.tsx Removed Radix Slot/Label usage; reworked FormControl cloning behavior.
src/components/admin/filter-form.tsx Updated dropdown trigger to Base UI render pattern.
src/components/admin/error.tsx Updated accordion prop usage for Base UI.
src/components/admin/data-table.tsx Updated tooltip trigger to Base UI render pattern.
src/components/admin/columns-button.tsx Migrated popover implementation to Base UI Popover + portal usage changes.
src/components/admin/bulk-export-button.tsx Tightened onClick event typing.
src/components/admin/breadcrumb.tsx Updated drawer close button styling approach.
src/components/admin/boolean-input.tsx Updated Switch onFocus typing.
src/components/admin/autocomplete-input.tsx Updated popover typing/imports for Base UI Popover.
src/components/admin/authentication.tsx Updated Button-as-link to Base UI render pattern.
src/components/admin/app-sidebar.tsx Updated SidebarMenuButton to Base UI render pattern.
scripts/build_registry.mjs Updated shadcn CLI command for v4.
registry.json Updated registry dependencies/files list for new structure.
package.json Removed Radix deps; added Base UI + updated shadcn version.
package-test.json Updated shadcn version used in tests.
eslint.config.js Expanded ignored build output directories.
docs/src/content/docs/AppSidebar.md Updated docs examples to render pattern.
components.json Updated shadcn config style + new config fields.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

src/components/ui/sidebar.tsx:490

  • These Tailwind classes use a trailing ! (e.g. size-8!, p-2!, p-0!). Tailwind's important modifier is a ! prefix, so these will be ignored and the collapsed/icon sizing overrides likely won't work.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +242 to +246
<a
href="https://github.com/marmelab/shadcn-admin-kit"
target="_blank"
/>
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Links opened with target="_blank" should include rel="noopener noreferrer" to prevent reverse-tabnabbing and to avoid leaking window.opener. Please add rel on this anchor.

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +78
<img
src={GithubLogo}
alt="Github"
className="inline h-4 w-auto"
/>{" "}
Star us on Github!
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brand spelling: the GitHub name should be capitalized as "GitHub" (alt text + visible label).

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +19
<Button
className="mt-10"
size="lg"
variant="outline"
render={<a href="https://marmelab.com/shadcn-admin-kit/docs" target="_blank" />}
nativeButton={false}
>
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Links opened with target="_blank" should include rel="noopener noreferrer" to prevent reverse-tabnabbing and to avoid leaking window.opener. Please add rel on this anchor.

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +62
{children}
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-primary fill-primary data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
</TooltipPrimitive.Popup>
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class list uses top-1/2! (and similar) which is not valid Tailwind syntax for the important modifier. Tailwind expects !top-1/2; as-is these positioning rules for the arrow won't be generated/applied.

Copilot uses AI. Check for mistakes.
Comment on lines 71 to +74
return (
<div
data-slot="command-input-wrapper"
className="flex h-9 items-center gap-2 border-b px-3"
>
<SearchIcon className="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
<div data-slot="command-input-wrapper" className="p-1 pb-0">
<InputGroup className="h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!">
<CommandPrimitive.Input
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several classes use a trailing ! (e.g. h-8!, rounded-xl!, shadow-none!). Tailwind's important modifier is a ! prefix (e.g. !h-8), so these will be ignored and the intended overrides won't apply.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +10
orientation = "horizontal",
decorative = true,
decorative = false,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
}: SeparatorPrimitive.Props & { decorative?: boolean }) {
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decorative now defaults to false, which makes separators exposed to assistive tech by default. In most UI usages separators are decorative; consider defaulting back to true (or making callers opt into non-decorative separators) to avoid accessibility noise/regressions.

Copilot uses AI. Check for mistakes.
Comment on lines 151 to 170
const resolveContainer = () => {
const target = document.getElementById(elementId);
if (target) {
setContainer(target);
return true;
}

return false;
};

if (typeof document === "undefined" || resolveContainer()) {
return;
}

// look for the container in the DOM every 100ms
const interval = setInterval(() => {
const target = document.getElementById(elementId);
if (target) setContainer(target);
if (resolveContainer()) {
clearInterval(interval);
}
}, 100);
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DOM polling setInterval has no upper bound now; if the target element never appears, this interval will run indefinitely while the component stays mounted. Please add a timeout/max-attempts (or switch to a MutationObserver) so this can't leak work in edge cases.

Copilot uses AI. Check for mistakes.
Comment on lines 73 to 75
size="default"
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
className={cn("pl-2!", className)}
{...props}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tailwind's important modifier uses a ! prefix (e.g. !pl-2), not a suffix. pl-2! will be ignored, so the left padding override likely won't apply.

Copilot uses AI. Check for mistakes.
Comment on lines 91 to 93
size="default"
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
className={cn("pr-2!", className)}
{...props}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tailwind's important modifier uses a ! prefix (e.g. !pr-2), not a suffix. pr-2! will be ignored, so the right padding override likely won't apply.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +29
<a
href="https://marmelab.com/shadcn-admin-kit/docs/install/"
target="_blank"
/>
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Links opened with target="_blank" should include rel="noopener noreferrer" to prevent reverse-tabnabbing and to avoid leaking window.opener. Please add rel on this anchor (and any similar ones in this component).

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@slax57 slax57 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lastly, I can still find references to radix in:

  • README.md
  • SKILLS.md
  • select-input.tsx (in a comment)
  • Technos.tsx (website)

Other than that, I tested several components, including the demo, storybook etc. and they all looked well! Great job!

<SidebarMenuItem>
<SidebarMenuButton
asChild
render={<Link to="/" />}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a clue some changes might be needed at the user-level too. So IMO this new feature deserves to be released in a new major version.

Besides, we need to document the migration to Base UI IMO. We can for instance link to shadcn-ui/ui#9562 since there seems to be no official migration guide (see shadcn-ui/ui#9142).

Comment on lines -159 to -160
// stop looking after 500ms
const timeout = setTimeout(() => clearInterval(interval), 500);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FMI why did you remove that? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the Base UI migration, the columns menu stop appearing. I changed the implementation to fix that regression, and then adjusted it again after Copilot's review to keep the fix while adding a bounded stop condition for the polling.

"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"shadcn": "^2.5.0",
"shadcn": "4.0.3",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"shadcn": "4.0.3",
"shadcn": "^4.0.3",

package.json Outdated
"ra-data-json-server": "^5.13.6",
"react-error-boundary": "^6.1.0",
"shadcn": "3.8.5",
"shadcn": "4.0.3",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"shadcn": "4.0.3",
"shadcn": "^4.0.3",

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still see some radix packages in that file. Is that to be expected? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is expected.
pnpm-lock.yaml still contains some transitive Radix packages coming from third-party dependencies, mainly cmdk and vaul.

registry.json Outdated
Comment on lines +374 to +393
{
"path": "src/components/admin/confirm.tsx",
"type": "registry:component"
},
{
"path": "src/components/admin/guesser-empty.tsx",
"type": "registry:component"
},
{
"path": "src/components/admin/icon-button-with-tooltip.tsx",
"type": "registry:component"
},
{
"path": "src/components/admin/select-all-button.tsx",
"type": "registry:component"
},
{
"path": "src/components/admin/spinner.tsx",
"type": "registry:component"
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catches!

registry.json Outdated
Comment on lines +394 to +409
{
"path": "src/components/ui/accordion.tsx",
"type": "registry:component"
},
{
"path": "src/components/ui/alert.tsx",
"type": "registry:component"
},
{
"path": "src/components/ui/avatar.tsx",
"type": "registry:component"
},
{
"path": "src/components/ui/badge.tsx",
"type": "registry:component"
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add shadcn/ui components as part of our registry? I don't understand... 🤔

Copy link
Contributor Author

@WiXSL WiXSL Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This became necessary after the Base UI migration.
These blocks rely on the local wrappers used by shadcn-admin-kit, not only on generic shadcn/ui components. Without that, shadcn add was pulling mismatched implementations and the generated app was failing to build.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these become shadcn-admin-kit wrappers, then I'd advocate in favor of moving them under src/components/admin, to make it clear they now belong to the kit and need to be maintained by us. Nevertheless I'm still surprised there are no components we can put as registryDependencies. How do we know what components come from shadcn/ui and what components were written by us?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree this was too aggressive a strategy. I’ll try to revert it.


For example, to customize the app name and logo, edit the `SidebarHeader` section:

```tsx {2,22-25}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

highlighted lines need to be updated accordingly

Comment on lines +39 to +46
<DropdownMenuItem
className="m-1"
render={
<a
href="https://marmelab.com/shadcn-admin-kit/demo"
target="_blank"
/>
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: small regression: the demo links used to show a clickable pointer

Image

vs

Image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's related to this file or not, but in the demo, the Status selector in the Reviews List is missing the colored badge, and it's a bit too narrow:

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a project-level regression in the Reviews status filter rather than a generic AutocompleteInput issue, so I fixed it at the usage site to restore the previous behavior without making the shared component more opinionated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants