Conversation
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 items, keeping only their root, trigger, and surface. The available-height, anchor-width, and transform-origin variables are named the same on every positioner, so the per-surface CSS mappings collapse. Styling and behavior stay as they were, verified by measuring both builds side by side: the select list, both menus, their submenus, and every tooltip land on the pixels they did before. Where Base UI differs it is corrected here rather than absorbed: - Select.Icon passes a literal arrow as default children, which merged into the codicon instead of replacing it, so the trigger renders the icon directly. - Base UI centers a popup on its anchor; the select list and context menu align to its start again, as Radix had them. - A select row scrolled into view parked against the border because the popup scrolls itself and its padding is scrollable space, so scroll-padding restores the inset Radix got from a nested viewport. - The tooltip is pinned to its own layer. Base UI only hints compositing above 1.5x, so below that its text picked up subpixel antialiasing and fringed against the hover background. - Base UI leaves the tooltip out of the accessibility tree and names a select option from its whole contents, so the tooltip regains role="tooltip" and aria-describedby, and an option is labelled by its text rather than its description. Stories in the package 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 22, 2026 15:06
49eaa91 to
b60546d
Compare
Radix anchored its popups with a fixed strategy; Base UI defaults to absolute. A fixed positioner is promoted to its own layer, and since that layer is not opaque Chromium rasterizes the text inside it with greyscale antialiasing. On absolute the tooltip picked up subpixel antialiasing instead, fringing its glyphs orange and blue. The select list scrolls a viewport inside the padded panel again, as it did under Radix. The panel holds the padding and background while the rows scroll within it, which puts the last row back against the list's padding rather than the border, where the corner radius clipped it. The stories reach their state declaratively rather than by tabbing to a trigger, which needs the window focused, or arrowing into a submenu, which left the first row's highlight up to when the screenshot landed. Focusing an element directly keeps the focus ring and the highlight without the timing, and the long list names its last pool so the trigger keeps the truncation the story exists to show. Every overlay story now renders identically run to run.
EhabY
force-pushed
the
ui-base-ui-migration
branch
from
September 22, 2026 16:09
16b6bb0 to
06dc41e
Compare
This branch has not been deployed
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.
Swaps the five
@radix-ui/*packages inpackages/uifor@base-ui/react. Styling and behavior are meant to be unchanged; the differences that could not be preserved are listed below.Why now
Nothing outside
packages/uiimports it yet, and the menus andSelectappear only in stories and tests. This is the cheapest this swap will ever be.What the switch bought
@radix-uipackages in the store) become one with five transitive deps.ContextMenuandDropdownMenuno longer build identical items from separate scopes. They keep only their root, trigger, and surface; everything else is shared asMenu*. ThemenuPartfactory existed to bridge the two Radix scopes and is gone, replaced by plainly typed components.--available-height,--anchor-width, and--transform-originare named the same on every positioner, so the per-surface CSS mappings and thevar(a, var(b))fallback collapse.Selectviewport scrollbar overrides existed only to undo Radix's scroll buttons, and are deleted.Differences that could not be preserved
Selecttrigger labels. Radix rendered the selected item'sItemText. Base UI renders the raw value unlessitemsdeclares the labels, and it only mounts items while open, so no wrapper can recover this. Callers now passitems(or achildrenfunction onSelectValue).placeholderis unchanged.DropdownMenuLabel/ContextMenuLabel. Base UI'sMenu.GroupLabelthrows outside aMenu.Group.MenuLabelrenders the same plaindivRadix'sLabeldid.TooltipProvider.delayDurationis nowdelay, matching the underlying prop.onValueChange/onOpenChangegain a secondeventDetailsargument.Accessibility restored on top of Base UI
Two things Base UI omits that Radix provided, both covered by tests:
role="tooltip"and noaria-describedby, leaving hovers out of the accessibility tree entirely. Both are wired back up, and the hooks that do it sit behind the hover-delegate early return so the tree's per-row tooltips do not pay for them.SelectItemwith adescriptiontook its accessible name from its whole contents, so the muted second line was announced twice. Items are now labelled by their text.Verification
pnpm typecheck,pnpm lint,pnpm test(2758 passed),pnpm build, andpnpm storybook:buildall pass.Visual parity still needs a Pixel run — that is the real check and I could not run it locally. To make a silent break less likely,
test/webview/ui/overlays.test.tsxasserts that each surface still carries the classes,data-open, and positioner variables the stylesheets read.Also in this branch
A second commit takes behavior assertions out of the
packages/uistories, leaving their play functions to interact and wait only. The two checks nothing else covered moved to unit tests.