refactor(ui): move the UI components from Radix to Base UI - #1123
Merged
Merged
Conversation
EhabY
force-pushed
the
ui-base-ui-migration
branch
2 times, most recently
from
September 22, 2026 16:09
16b6bb0 to
06dc41e
Compare
EhabY
marked this pull request as ready for review
September 22, 2026 17:51
EhabY
force-pushed
the
ui-base-ui-migration
branch
from
September 23, 2026 08:36
543fd11 to
04d6a76
Compare
Base UI serves the menu, context menu, select, and tooltip from one package, and its context menu reuses the menu's own parts. ContextMenu and DropdownMenu now share one set of Menu* items, keeping only their root, trigger, and surface. Every positioner names its available-height, anchor-width, and transform-origin variables the same way, so the per-surface CSS mappings collapse into one overlay rule. Styling and behavior stay as they were, verified by measuring both builds side by side. Where Base UI differs, this change corrects it: - Every overlay positions with a fixed strategy. A fixed positioner gets its own layer, which keeps the text on greyscale antialiasing instead of subpixel fringing. - The select list and context menu align to their anchor's start, and the context menu sets its side explicitly to skip Base UI's context-menu offsets. - The select list scrolls a viewport inside the padded panel, so the last row sits against the padding rather than under the corner radius. - Select.Icon renders a literal arrow as default children, so the trigger renders the codicon directly. - Base UI sets no tooltip role or aria-describedby and names a select option from its whole contents, so the tooltip regains both and an option is labelled by its text rather than its description. The workarounds Radix needed are gone: TooltipScope, since a Base UI tooltip works without a provider; the jsdom pointer-capture and scrollIntoView stubs; and the tree's hidden hover anchor span. Tree rows still hand their hovers to one shared bubble, because a Base UI trigger per row costs about five times as much to mount. The bubble now positions against a virtual anchor measured from the tree. The hover delegate types are no longer exported. Only Tree uses them, so the public tooltip API is Tooltip and TooltipProvider. The overlay APIs are stricter than the Radix ones were: - MenuContent and SelectContent forward every prop to the menu or listbox element, and take only side, align, and offsets for placement. - Select requires items, since Base UI builds the trigger label from them and would otherwise show the raw value, and takes a single value. - MenuLabel builds on Menu.GroupLabel and names the MenuGroup or MenuRadioGroup it sits in. Stories drive a component into the state Pixel photographs, so their play functions only interact and wait. The checks nothing else covered moved to unit tests.
EhabY
force-pushed
the
ui-base-ui-migration
branch
from
September 23, 2026 08:54
04d6a76 to
21fc063
Compare
jakehwll
approved these changes
Sep 23, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the five
@radix-ui/*packages inpackages/uiwith@base-ui/react. Styling and behavior stay as they were. Where Base UI differs, this PR corrects it or makes the API stricter, as listed in the following sections.Why now
Nothing outside
packages/uiimports it yet, and the menus andSelectappear only in stories and tests, so the swap won't get cheaper than this.What the switch buys
ContextMenuandDropdownMenukeep only their root, trigger, and surface, and share every item asMenu*. ThemenuPartfactory that bridged the two Radix scopes is gone.--available-height,--anchor-width, and--transform-originthe same way, so the per-surface CSS mappings collapse into one overlay rule.TooltipScope(a Base UI tooltip works without a provider), the jsdom pointer-capture andscrollIntoViewstubs, theSelectscrollbar overrides, and the tree's hidden hover anchor<span>. The tree's shared hover bubble now positions against a virtual anchor measured from the tree.API changes
MenuContent,DropdownMenuContent,ContextMenuContent: these takeside,align,sideOffset, andalignOffsetfor placement, and forward every other prop, includingref, to the menu element.SelectContent:classNameandstyledress the panel, and every other prop goes to the listbox.Select: requiresitemsand takes a single value. Base UI builds the trigger label fromitems, so without it the trigger shows the raw value. The type now catches that, andSelectPropsis exported.MenuLabel: builds onMenu.GroupLabeland names the newMenuGroupor theMenuRadioGroupit sits in. Outside a group, Base UI throws.TooltipProvider:delayDurationis nowdelay, matching the underlying prop.onValueChangeandonOpenChange: these gain a secondeventDetailsargument.HoverDelegateScope,HoverDelegate, andHoverTargetare no longer exported. OnlyTreeuses them, so the public tooltip API isTooltipandTooltipProvider.Corrections on top of Base UI
sideexplicitly, which also skips Base UI's context-menu offsets.role="tooltip"oraria-describedbyon tooltips, and names a select option from its whole contents. Tooltips get both attributes back, and an option with adescriptionis labelled by its text alone.Performance
Tree rows still hand their hovers to one shared bubble instead of mounting a tooltip each. In a jsdom benchmark with 2,000 rows (mount plus one rerender), sharing costs about 40 ms with either library, a tooltip per row costs about 300 ms with Radix and about 225 ms with Base UI, and Base UI's detached triggers cost about 220 ms. The React Compiler memoizes the delegate, so moving the hover re-renders no rows.
Verification
pnpm typecheck,pnpm lint,pnpm test(2756 passed),pnpm build, andpnpm storybook:buildpass.<span>.test/webview/ui/overlays.test.tsxchecks that each surface forwards its props to the right element and carries the classes,data-open, and positioner variables the stylesheets read.Stories drive a component into the state Pixel photographs, so their play functions only interact and wait. The checks nothing else covered moved to unit tests.