Skip to content

chore: migrate ui primitives from radix to base ui - #182

Merged
Azganoth merged 3 commits into
mainfrom
chore/migrate-radix-to-base-ui
Aug 9, 2026
Merged

chore: migrate ui primitives from radix to base ui#182
Azganoth merged 3 commits into
mainfrom
chore/migrate-radix-to-base-ui

Conversation

@Azganoth

@Azganoth Azganoth commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replace radix-ui with @base-ui/react across src/components/ui/, and manage the project with the shadcn CLI through components.json, which relocates cn to src/lib/utils.ts.
  • Replace sonner with a Leafdown-owned Base UI toast. src/lib/toast.ts owns the toast manager and the notify* API so services outside the React tree can still raise notifications; src/components/ui/toast.tsx owns the provider, viewport, and presentation.
  • Remove the primitives no application code reached: Checkbox, ToggleGroup, and its internal Toggle.
  • Name files that export React components in kebab-case and leave other modules camelCase, and record that rule in src/AGENTS.md.
  • Record the decision in docs/decisions.md, and update src/AGENTS.md and docs/patterns.md to describe Base UI.

Behavior held at parity where Base UI's defaults differ from Radix's:

  • Dialogs still ignore presses on the titlebar drag region and the window controls, now expressed by cancelling an outside-press change rather than a pointer-down event.
  • Menubar checkbox and radio items still close the menu when chosen; Base UI leaves both open by default.
  • Toolbar controls still leave the roving sequence while disabled; Base UI keeps them in it.
  • The editor context popup keeps its selection anchoring, pointer-versus-keyboard focus ownership, row traversal, and focus return, and no longer depends on an undocumented positioning wrapper element.

Related Issue

Closes #167

Verification

  • pnpm check:frontend — passed for the frontend-only migration, including the coverage floor.
  • Dialog tests prove that a press on the titlebar drag region leaves a dialog open while an ordinary outside press still dismisses it. The dismissal case previously asserted against a handler that never fired, so it passed without proving anything.
  • Command menubar tests prove that submenus open, that a submenu whose commands are all unavailable stays closed, and that items, checkbox commands, and radio commands still reach the command layer.
  • Editor context popup tests prove that it positions against a measured selection rather than a rendered anchor, takes focus only for keyboard requests, skips unavailable controls when taking focus, walks rows with the vertical arrows, opens a submenu with ArrowDown instead of leaving its row, closes on Escape and Tab, returns focus to the editor, and hides while none of its selection is visible.
  • Notification tests prove that every notify* call reaches the toast manager with its title, description, and type.
  • pnpm build:frontend — produced the production bundle recorded under Notes.

Manually verified in pnpm tauri dev on Windows 11:

  1. Dialogs stay open while the window is dragged by the titlebar region and while the minimize, maximize, and close controls are used, and still dismiss on an ordinary outside press.
  2. The command menubar walks by keyboard across its menus and submenus, keeps unavailable commands disabled, and closes on checkbox and radio commands.
  3. The editor context popup anchors to the selection, takes focus only when opened from the keyboard, traverses its rows with the arrows, opens its submenus, dismisses on Escape and on scroll, and returns focus to the editor.

Not verified: Linux and macOS, which are not the current polish target.

Notes

  • The production bundle grows against 3b678ee: JavaScript from 1,781.26 kB to 1,828.64 kB (gzip 444.39 kB to 465.53 kB) and CSS from 92.98 kB to 99.10 kB (gzip 14.04 kB to 15.32 kB). Base UI Toast ships no default styling or fixed toast types, so the presentation Sonner supplied is now markup and CSS this repository owns, and that accounts for part of the increase.
  • Radix's type="hover" and type="scroll" scroll area modes have no Base UI equivalent. The scrollbar is always mounted and reveals itself from its own hover and scrolling state, so ScrollArea no longer takes a visibility mode.
  • The editor context popup handles keys during the capture phase. Base UI's composite items relay their keyboard events to the toolbar root instead of letting them bubble, so a handler on the popup would otherwise never see Escape, Tab, or the vertical arrows.
  • The toolbar now carries a single roving tab stop rather than holding every control outside the tab sequence. Tab is still intercepted and closes the popup, so the tab stop is not reachable from the editor.
  • MenubarTrigger regained the focus ring described in docs/patterns.md; the generated component suppressed the outline without replacing it.
  • Input and InputGroup remain without application consumers. They are not Radix-derived, so removing them is left out of this change.

@Azganoth Azganoth added the Maintenance Chore, refactor, dependency, or test work without user-facing change label Aug 9, 2026
@Azganoth Azganoth self-assigned this Aug 9, 2026
The naming rule as written sent paired tests and stylesheets to camelCase,
contradicting the files this branch renamed.

The decision entry recorded Base UI API mechanics as consequences. Those
belong to Base UI documentation and go stale whenever a wrapper overrides a
default, which two already do; the general rule replaces them.
@Azganoth
Azganoth force-pushed the chore/migrate-radix-to-base-ui branch from 164fa6a to 4c60192 Compare August 9, 2026 05:21
@Azganoth
Azganoth merged commit 2405bcb into main Aug 9, 2026
2 checks passed
@Azganoth
Azganoth deleted the chore/migrate-radix-to-base-ui branch August 9, 2026 05:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Maintenance Chore, refactor, dependency, or test work without user-facing change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Evaluate migrating UI primitives from Radix UI to Base UI

1 participant