Skip to content

refactor(ui): switch shadcn primitives from Radix to Base UI - #32124

Merged
ryan-crabbe-berri merged 6 commits into
litellm_internal_stagingfrom
litellm_shadcn_base_ui
Jul 7, 2026
Merged

refactor(ui): switch shadcn primitives from Radix to Base UI#32124
ryan-crabbe-berri merged 6 commits into
litellm_internal_stagingfrom
litellm_shadcn_base_ui

Conversation

@ryan-crabbe-berri

@ryan-crabbe-berri ryan-crabbe-berri commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

The chat page is the main observable surface since its components were converted:

  1. cd ui/litellm-dashboard && npm install (resolves cleanly, no peer flags needed)
  2. npx vitest run src/components/ui/button.test.tsx (5 tests, including the Base UI render-prop polymorphism and React 18 ref forwarding)
  3. npm run dev and load the chat page; exercise the converted surfaces: model selector popover, MCP servers popover, tooltip hover on message copy/edit buttons, conversation rename/delete (alert dialog), and the MCP credentials revoke dialog
  4. Load http://localhost:3000/?page=old-usage and open the date range picker to confirm tremor's DateRangePicker (the main runtime consumer of date-fns, now v4) still works

Type

🧹 Refactoring

Changes

shadcn made Base UI the default primitive library in July 2026 (https://ui.shadcn.com/docs/changelog/2026-07-base-ui-default). Radix, the previous default, has been in maintenance slowdown since its maintainers moved on, and Base UI 1.x is built by the same lineage of authors (ex-Radix and Floating UI, at MUI). Our shadcn foundation (#31995) landed with the Radix variant because that was the default at the time; with exactly one shadcn component in the tree (the Button canary), this is the last cheap moment to switch before the primitives phase adds the full component set

components.json style moves from new-york (a legacy alias that now resolves to the Radix variant radix-vega) to base-vega, so every future shadcn add pulls Base UI components. Button is regenerated from the base-vega registry via shadcn add button --overwrite and re-adapted the same way the original was: cva beta object form imported from lib/cva.config instead of class-variance-authority, and a React 18 forwardRef wrapper since registry templates assume React 19. The polymorphic asChild prop is replaced by Base UI's render prop; per Base UI semantics, rendering a non-button element requires nativeButton={false} and keeps role=button on the rendered element, which the updated test pins

radix-ui is replaced by @base-ui/react 1.6.0 (supports React 17/18/19). Base UI optionally peers on date-fns 4 while tremor hard-pins 3 through react-day-picker 8, which is unsolvable against a single hoisted copy, so date-fns is bumped to 4.4.0 with an npm override. Our only direct usage is add() in keyExpiryUtils.ts, which is API-identical across 3 and 4 (v4 only adds timezone support), and the override can be deleted when tremor is removed

Scope update: chat UI components converted too

After this branch was cut, the chat UI migration (11314f4) landed on the base branch with 11 components/ui files generated against the old Radix registry config (dialog, alert-dialog, select, popover, tooltip, tabs, switch, scroll-area, collapsible, separator, label). Merged as-is, this PR would have deleted the radix-ui package out from under them, so the conversion is now part of the switch: all 11 are regenerated from the base-vega registry via shadcn add --overwrite, tabs gets the same cva-beta adaptation as Button, and the chat feature's call sites move from Radix's asChild composition to Base UI's render prop (TooltipProvider delayDuration becomes delay). Component tests for the chat surfaces pass and the tree compiles with zero radix references left

Summary by CodeRabbit

  • New Features
    • Buttons now support updated rendering behavior for link-like usage while preserving button semantics.
  • Bug Fixes
    • Refined button styling across default, destructive, outline, secondary, ghost, and link variants.
    • Improved focus and size styling for more consistent visual behavior.
  • Chores
    • Updated dashboard styling configuration and refreshed several project dependencies.

shadcn made Base UI the default primitive library in July 2026 and our
only shadcn component so far is the Button canary, so this is the last
cheap moment to switch before the primitives phase adds the full set.

components.json style moves from new-york (a legacy alias that resolves
to the Radix variant) to base-vega. Button is regenerated from the
base-vega registry with the same local adaptations as before: cva beta
object form via lib/cva.config and a React 18 forwardRef wrapper. The
polymorphic asChild prop becomes Base UI's render prop.

radix-ui is replaced by @base-ui/react 1.6.0. Base UI optionally peers
on date-fns 4 while tremor pins 3, so date-fns is bumped to 4.4.0 with
an npm override; our only usage (add) is API-identical and the override
can go away when tremor does.
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the dashboard's Button component from Radix Slot-based composition to base-ui/react's ButtonPrimitive. It updates package.json dependencies (adds @base-ui/react, bumps date-fns, removes radix-ui), changes components.json style, rewrites Button's styling and prop typing, and updates related tests.

Changes

Button migration to base-ui

Layer / File(s) Summary
Dependency and config updates
ui/litellm-dashboard/package.json, ui/litellm-dashboard/components.json
Adds @base-ui/react dependency, updates date-fns to ^4.4.0, removes radix-ui dependency, simplifies overrides to only date-fns, and changes components.json style from new-york to base-vega.
Button component implementation
ui/litellm-dashboard/src/components/ui/button.tsx
Redefines buttonVariants Tailwind classes for base/variant/size combinations, changes Button prop typing to ButtonPrimitive.Props & VariantProps<typeof buttonVariants>, removes asChild/Slot-based rendering, and renders ButtonPrimitive directly with data-slot and computed className.
Button tests updated
ui/litellm-dashboard/src/components/ui/button.test.tsx
Removes data-variant attribute assertions, updates destructive variant class expectations to bg-destructive/10, and adds a new test for nativeButton={false} with render prop verifying anchor rendering with button role semantics.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Consumer
  participant Button
  participant ButtonPrimitive

  Consumer->>Button: render with variant/size/render props
  Button->>Button: compute className via buttonVariants
  Button->>ButtonPrimitive: pass data-slot, className, ref, props
  ButtonPrimitive-->>Consumer: rendered element (native button or custom via render prop)
Loading

Related Issues: None specified.

Related PRs: None specified.

Suggested labels: ui, dependencies, frontend

Suggested reviewers: None specified.

🐰 A hop, a skip, from Radix to base,
New buttons rendered, a cleaner face,
Slots gave way to primitives bright,
Classes reshuffled, styled just right,
The dashboard button now runs the race!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: switching shadcn primitives from Radix to Base UI.
Description check ✅ Passed The description follows the template and includes the required sections, checklist, proof steps, type, and change summary.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch litellm_shadcn_base_ui

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR completes the migration of the shadcn UI foundation from Radix UI to Base UI 1.x, updating components.json to the base-vega registry style and regenerating all 12 existing UI primitives (button, alert-dialog, dialog, popover, select, tooltip, tabs, switch, scroll-area, collapsible, separator, label) plus updating all chat call sites from the asChild composition pattern to Base UI's render prop.

  • All 12 UI components are regenerated from the base-vega registry and adapted for React 18 (forwardRef wrapper, render prop instead of asChild); radix-ui is fully removed with no remaining references.
  • date-fns is bumped to v4.4.0 with an npm override to resolve Base UI's peer dependency; a new render test for the tremor DateRangePicker confirms the date-fns v3-authored code paths used by tremor remain compatible under the override.
  • New tests cover the alert-dialog confirm/cancel flows and the updated button render-prop polymorphism; the data-variant assertions removed from button.test.tsx correctly reflect the deliberate removal of that attribute from the component.

Confidence Score: 5/5

The change is a contained UI primitive swap with no backend or auth surface modifications; the only production risk is visual regressions in components that are already tested.

All 12 UI components are migrated consistently, Radix references are fully removed, call sites are updated throughout the chat feature, and new/updated tests cover the converted surfaces. The two findings are cosmetic: dead Radix selectors in the tooltip class string, and the Tabs orientation prop not forwarded to TabsPrimitive.Root (only affects vertical tab layouts, none of which are used in the current codebase).

ui/litellm-dashboard/src/components/ui/tabs.tsx — the orientation prop is extracted but not passed to TabsPrimitive.Root, so data-horizontal/data-vertical CSS rules will not apply if vertical tabs are ever added.

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/components/ui/tabs.tsx Migrated from Radix to Base UI Tabs; orientation prop is destructured but not forwarded to TabsPrimitive.Root, leaving data-horizontal/data-vertical CSS selectors inert for non-default orientations.
ui/litellm-dashboard/src/components/ui/tooltip.tsx Migrated from Radix to Base UI Tooltip; correctly adopts Positioner/Popup split and delay prop, but retains dead data-[state=delayed-open] selectors from the old Radix template.
ui/litellm-dashboard/src/components/ui/button.tsx Swapped Radix Slot for Base UI ButtonPrimitive; removed asChild in favour of the render prop; cva variants and React 18 forwardRef wrapper preserved correctly.
ui/litellm-dashboard/src/components/ui/alert-dialog.tsx Clean migration to Base UI AlertDialog; Overlay→Backdrop, Content→Popup, Action/Cancel both map to Close (correct for Base UI API).
ui/litellm-dashboard/src/components/ui/dialog.tsx Clean migration to Base UI Dialog; Overlay→Backdrop, Content→Popup, close button now uses render prop composition.
ui/litellm-dashboard/src/components/ui/select.tsx Migrated to Base UI Select; ScrollUpButton/ScrollDownButton replaced with ScrollUpArrow/ScrollDownArrow, SelectLabel updated to GroupLabel.
ui/litellm-dashboard/package.json radix-ui removed, @base-ui/react@^1.6.0 added, date-fns bumped to ^4.4.0 with override; security pins preserved in the overrides block.

Reviews (3): Last reviewed commit: "fix(ui): close the alert dialog when Ale..." | Re-trigger Greptile

Comment thread ui/litellm-dashboard/package.json
Comment thread ui/litellm-dashboard/package.json
@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
ui/litellm-dashboard/src/components/ui/button.test.tsx (1)

32-38: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Use a neutral element here instead of <a>
This test can demonstrate nativeButton={false} without modeling the discouraged link-as-button pattern; a <span> or <div> keeps the example aligned with Base UI guidance.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/litellm-dashboard/src/components/ui/button.test.tsx` around lines 32 - 38,
The Button render-prop test currently uses an anchor element, which models a
discouraged link-as-button pattern. Update the test in button.test.tsx to use a
neutral element such as a span or div with Button nativeButton={false} and
render, while keeping the same button-role assertions and class/href
expectations that verify the Button component behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/litellm-dashboard/package.json`:
- Around line 39-46: The package override is forcing date-fns v4 into Tremor’s
dependency tree even though `@tremor/react`@3.18.7 expects date-fns v3, which can
break date components at runtime. Update the package.json dependency/override
section to either remove the date-fns ^4.4.0 override or change Tremor to a
version that is compatible with date-fns v4, and verify the dependency
resolution around `@tremor/react` stays on a supported date-fns major version.

---

Nitpick comments:
In `@ui/litellm-dashboard/src/components/ui/button.test.tsx`:
- Around line 32-38: The Button render-prop test currently uses an anchor
element, which models a discouraged link-as-button pattern. Update the test in
button.test.tsx to use a neutral element such as a span or div with Button
nativeButton={false} and render, while keeping the same button-role assertions
and class/href expectations that verify the Button component behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d3a14083-ff02-4826-80f0-9bb1d8db30d2

📥 Commits

Reviewing files that changed from the base of the PR and between 47f493a and ee3defb.

⛔ Files ignored due to path filters (1)
  • ui/litellm-dashboard/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • ui/litellm-dashboard/components.json
  • ui/litellm-dashboard/package.json
  • ui/litellm-dashboard/src/components/ui/button.test.tsx
  • ui/litellm-dashboard/src/components/ui/button.tsx

Comment thread ui/litellm-dashboard/package.json
Comment thread ui/litellm-dashboard/package.json
@veria-ai

veria-ai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

The chat UI migration landed 11 components/ui files generated against
the old Radix registry config after this branch cut over to Base UI,
which would have left them importing a deleted package. All 11 (dialog,
alert-dialog, select, popover, tooltip, tabs, switch, scroll-area,
collapsible, separator, label) are regenerated from the base-vega
registry, with the repo conventions re-applied where relevant (cva beta
object form from lib/cva.config in tabs; the Button canary keeps its
React 18 forwardRef adaptation).

Chat feature call sites move from the Radix asChild pattern to Base
UI's render prop, and TooltipProvider delayDuration becomes delay.
…rride

The date-fns 4 override was written by replacing the whole overrides
object, dropping the ten security pins (prismjs, js-yaml, glob,
minimatch, lodash, ws, braces, axios, postcss, esbuild) that keep
patched versions in the lockfile; osv-scan caught the vulnerable
versions resurfacing. Restores the pins alongside date-fns and
regenerates the lockfile.
@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

@greptileai re review

The date-fns 4 override forces react-day-picker 8 (authored against v3)
onto v4 at runtime, which a build or lint pass cannot validate. This
renders the shared UsageDatePicker wrapper, opens the calendar, checks
the month grid, and selects a day, so a date-fns API break in the
tremor date path fails tests instead of throwing in production. Delete
alongside the override when tremor is removed.
The base-vega registry template renders AlertDialogAction as a plain
Button with no Close binding, so confirm buttons fired their onClick
but left the dialog open; both consumers (conversation delete,
MCP credential revoke) were written against the Radix semantics where
Action dismisses on click. Binds Action to AlertDialogPrimitive.Close
via the render prop, mirroring AlertDialogCancel, and pins the
behavior with a test so a future shadcn add --overwrite cannot
silently reintroduce the template's non-closing Action.
@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

@greptileai re review

@ryan-crabbe-berri
ryan-crabbe-berri merged commit cfe9e39 into litellm_internal_staging Jul 7, 2026
125 checks passed
@ryan-crabbe-berri
ryan-crabbe-berri deleted the litellm_shadcn_base_ui branch July 7, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants