You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Should Leafdown progressively migrate its existing UI primitive layer from radix-ui to @base-ui/react, remain on Radix, or defer the decision until a concrete Base UI capability is needed?
Distinguish shadcn’s recommendation for new projects from the value and risk of changing an established desktop application.
Context
In July 2026, shadcn made Base UI the default for new projects, citing Base UI 1.6 stability, adoption, and continued development. The same announcement says Radix is not deprecated, remains fully supported, and existing applications do not need to migrate. Base UI as the default
The surrounding releases show that shadcn now treats the primitive library as a project choice:
Complete Base UI component documentation and side-by-side examples shipped in January. Base UI documentation
CLI v4 added explicit base selection and tooling for inspecting component differences. shadcn CLI v4
React Aria later became a third supported base while Base remained the default and existing projects remained unchanged. React Aria support
Radix continues to receive accessibility, React 19, behavior, and tree-shaking improvements. Radix releases
shadcn also provides a dedicated Radix UI → Base UI agent migration skill. It supports hand-owned wrappers, migrates components progressively while both libraries coexist, checks consumer API differences, flags behavior changes, and produces per-component verification reports. If migration is selected, Leafdown should use this workflow instead of treating the change as a codemod. Dedicated migration workflow
Leafdown is not currently a CLI-managed shadcn project. As of 2026-08-02, pnpm dlx shadcn@latest info --json reports no components.json, preset, or detected installed components. Therefore, the new default does not change Leafdown automatically and migration would not be a configuration flip.
Contains 16 owned wrappers importing radix-ui; 13 are reachable from current application code. Checkbox, ToggleGroup, and its internal Toggle dependency have no application consumers.
Already has shared state-attribute variants that recognize both Radix and Base UI conventions in App.css.
Currently records Radix as the preferred accessible primitive library in src/AGENTS.md. Selecting Base UI would therefore change durable project direction.
The main migration risk is behavioral rather than visual:
Base UI replaces asChild with render.
Popup anatomy changes from Content to Portal > Positioner > Popup.
Focus and dismissal interception move from content callbacks to root change-event details and initialFocus/finalFocus.
Menu items replace onSelect with onClick and explicit closeOnClick; checkbox and radio menu items have different close defaults.
Base UI Menubar delegates its menus to the separate Menu primitive.
Radix Popover Anchor becomes the Base UI Positioner’s anchor input.
Base UI Separator is always semantic, while Leafdown’s wrapper defaults to decorative separators.
These differences directly touch three behavior-sensitive areas:
The editor context popup, including its virtual selection anchor, manual focus ownership, toolbar navigation, tooltips, and nested menu.
The application command menubar, including disabled commands, submenus, checkbox/radio items, keyboard navigation, and menu-closing behavior.
The dialog wrapper, which prevents titlebar drag and window-control interactions from dismissing dialogs in the Tauri window.
No existing Leafdown issue mentioning Radix, Base UI, shadcn, or primitive migration was found.
Exit criteria
The active and unused Radix wrapper inventory is recorded.
The behavior and API differences relevant to Leafdown are documented.
The dialog, command menubar, and editor context popup have automated verification evidence, and the manual pass is named as outstanding. No prototype was built; the migration was implemented directly.
Bundle and dependency comparisons are recorded.
The outcome selects Radix, progressive migration, or a defined deferral trigger.
The outcome records the conclusion, tradeoffs, rejected alternatives, and supporting evidence links.
No follow-up issues were created; the selected work is one pull request.
Required updates to src/AGENTS.md or other owning documentation are identified.
Outcome
Decision: migrate, delivered as a single change. Leafdown moves its UI primitive layer from radix-ui to @base-ui/react, adopts shadcn CLI project management, and replaces sonner with Base UI Toast. Implemented manually in #182.
Verified against the manifests, the code, and upstream sources on 2026-08-07. Corrections to Context above:
Base UI is at 1.7.0, not 1.6.npm view @base-ui/react version reports 1.7.0, published 2026-08-04. Minor releases have shipped monthly since 1.0.0 on 2025-12-11. Base UI releases records no breaking changes in 1.6.0 or 1.7.0; the last breaking changes were in 1.0.0-rc.0.
The package name is confirmed, and the old one is retired.@base-ui/react is correct. npm view @base-ui-components/react deprecated returns Package was renamed to @base-ui/react, with that name frozen at 1.0.0-rc.0. The migration must not follow older @base-ui-components/react documentation or examples.
Radix is current and active.radix-ui@1.6.7 is both the installed version and the latest published stable; 1.7.0-rc builds were still shipping on 2026-07-31. Radix releases shows fixes through 2026-07-20 covering Dialog, Tooltip, dismissable layers, React 19.2 compatibility, and per-primitive subpath entry points. No deprecation or maintenance-mode notice exists. The migration is therefore elective, not forced.
shadcn's stance is unchanged.Base UI as the default still states that Radix is not being deprecated and that existing projects do not need to migrate.
The wrapper inventory is confirmed: 16 wrappers import radix-ui, 13 have application consumers, and Checkbox, ToggleGroup, and its internal Toggle dependency have none.
Two couplings not listed in Context, both removed rather than ported:
Radix positioning internals were load-bearing in three places — the editor context popup resolved [data-radix-popper-content-wrapper] to drive its repositioning transition, a stylesheet styled that wrapper, and a test asserted against it. Base UI's Positioner is an element the application owns, so all three bindings disappear. This is the migration's clearest structural gain.
Four wrappers hardcoded Radix transform-origin custom properties — Popover, Tooltip, DropdownMenu, and Menubar each consumed --radix-*-content-transform-origin, six occurrences. Base UI exposes --transform-origin on the positioner.
Baseline
pnpm check:frontend passes at 3b678ee: oxfmt, tsc -b, oxlint type-aware, build, and vitest run --coverage at 90.79% statements, 81.59% branches, 92.17% functions, 92.08% lines.
Artifact
Raw
Gzip
dist/assets/index-*.js
1,781.26 kB
444.39 kB
dist/assets/index-*.css
92.98 kB
14.04 kB
Dependency footprint: radix-ui@1.6.7 resolved to 60 @radix-ui/* packages, about 5.4 MB installed. The application ships as a single JavaScript chunk dominated by the editor and syntax-highlighting stack, and Radix's share of it was not isolated.
Rejected alternatives
Defer with a trigger, or stay on Radix. Cheaper on cost alone, since Radix is current and unblocking, but both leave Leafdown on a diverging ecosystem default and keep the [data-radix-popper-content-wrapper] coupling indefinitely. The migration cost is accepted against that.
Progressive migration, with Radix and Base UI coexisting across several changes. Coexistence doubles the primitive surface, the state-attribute conventions, and the reviewer's burden for its duration, and the cost is concentrated in re-verifying interaction behavior that is easier to judge once, whole, than in partial states.
Permanent mixed Radix/Base policy. Coexistence is a within-branch mechanism only.
Scope delivered
All 13 active wrappers moved to @base-ui/react; Checkbox, ToggleGroup, and Toggle were deleted rather than migrated.
sonner replaced by Base UI Toast. src/lib/toast.ts owns the toast manager and the notify* API so callers outside the React tree keep working; the component owns provider, viewport, and presentation.
shadcn CLI project management adopted through components.json, which relocated cn to src/lib/utils.ts. The wrappers stay hand-owned.
Files exporting React components renamed to kebab-case, with the rule recorded in src/AGENTS.md.
How the open questions resolved
The decision was taken on the measured surface rather than on a prototype, so these were carried into implementation as risks:
Toolbar.Button defaulting focusableWhenDisabled to true was overridden in the wrapper, keeping disabled controls out of the roving sequence so vertical row movement and horizontal roving agree on what is reachable.
The Tauri titlebar and window-control exception is expressible through onOpenChange, by cancelling an outside-press change rather than a pointer-down event.
Base UI's composite items relay keyboard events to the toolbar root instead of letting them bubble. This was not anticipated: the editor context popup handles keys during the capture phase because a handler on the popup never sees Escape, Tab, or the vertical arrows.
Separators stay semantic. The Radix wrapper defaulted to decorative, but that was a template default rather than a Leafdown requirement, and all four call sites separate real content groups. No override was added.
The swap costs bundle size. JavaScript grew to 1,828.64 kB (gzip 465.53 kB) and CSS to 99.10 kB (gzip 15.32 kB). Base UI Toast ships no default styling or fixed toast types, so presentation Sonner supplied is now markup and CSS this repository owns.
Verification
#182 records the evidence. pnpm check:frontend passes, and focused tests prove dialog dismissal against the titlebar drag region, command menubar submenu and close behavior, the editor context popup's anchoring, focus ownership, row traversal and dismissal, and that every notify* call reaches the toast manager.
The three behavior-sensitive surfaces were also exercised by hand in pnpm tauri dev on Windows 11: dialog dismissal against real window drag and window controls, the command menubar keyboard walk, and the editor context popup in the real WebView. Linux and macOS remain unverified, as they are not the current polish target.
Documentation updated
docs/decisions.md carries the decision under Technical Decisions.
src/AGENTS.md records Base UI as the preferred primitive library, the relocated cn path, and the file naming rule.
docs/patterns.md no longer describes focus and traversal behavior in Radix's terms.
No changelog entry: the primitive migration is not user-facing, and the toast restyle was judged not notable.
Question
Should Leafdown progressively migrate its existing UI primitive layer from
radix-uito@base-ui/react, remain on Radix, or defer the decision until a concrete Base UI capability is needed?Distinguish shadcn’s recommendation for new projects from the value and risk of changing an established desktop application.
Context
In July 2026, shadcn made Base UI the default for new projects, citing Base UI 1.6 stability, adoption, and continued development. The same announcement says Radix is not deprecated, remains fully supported, and existing applications do not need to migrate. Base UI as the default
The surrounding releases show that shadcn now treats the primitive library as a project choice:
shadcn also provides a dedicated Radix UI → Base UI agent migration skill. It supports hand-owned wrappers, migrates components progressively while both libraries coexist, checks consumer API differences, flags behavior changes, and produces per-component verification reports. If migration is selected, Leafdown should use this workflow instead of treating the change as a codemod. Dedicated migration workflow
Leafdown is not currently a CLI-managed shadcn project. As of 2026-08-02,
pnpm dlx shadcn@latest info --jsonreports nocomponents.json, preset, or detected installed components. Therefore, the new default does not change Leafdown automatically and migration would not be a configuration flip.The current repository:
radix-ui@^1.6.7.radix-ui; 13 are reachable from current application code.Checkbox,ToggleGroup, and its internalToggledependency have no application consumers.App.css.src/AGENTS.md. Selecting Base UI would therefore change durable project direction.The main migration risk is behavioral rather than visual:
asChildwithrender.ContenttoPortal > Positioner > Popup.initialFocus/finalFocus.onSelectwithonClickand explicitcloseOnClick; checkbox and radio menu items have different close defaults.anchorinput.These differences directly touch three behavior-sensitive areas:
Related context
src/components/uiNo existing Leafdown issue mentioning Radix, Base UI, shadcn, or primitive migration was found.
Exit criteria
src/AGENTS.mdor other owning documentation are identified.Outcome
Decision: migrate, delivered as a single change. Leafdown moves its UI primitive layer from
radix-uito@base-ui/react, adopts shadcn CLI project management, and replacessonnerwith Base UI Toast. Implemented manually in #182.Baseline:
3b678ee, 2026-08-07.Revalidated assumptions
Verified against the manifests, the code, and upstream sources on 2026-08-07. Corrections to
Contextabove:npm view @base-ui/react versionreports1.7.0, published 2026-08-04. Minor releases have shipped monthly since 1.0.0 on 2025-12-11. Base UI releases records no breaking changes in 1.6.0 or 1.7.0; the last breaking changes were in1.0.0-rc.0.@base-ui/reactis correct.npm view @base-ui-components/react deprecatedreturnsPackage was renamed to @base-ui/react, with that name frozen at1.0.0-rc.0. The migration must not follow older@base-ui-components/reactdocumentation or examples.radix-ui@1.6.7is both the installed version and the latest published stable;1.7.0-rcbuilds were still shipping on 2026-07-31. Radix releases shows fixes through 2026-07-20 covering Dialog, Tooltip, dismissable layers, React 19.2 compatibility, and per-primitive subpath entry points. No deprecation or maintenance-mode notice exists. The migration is therefore elective, not forced.radix-ui, 13 have application consumers, andCheckbox,ToggleGroup, and its internalToggledependency have none.Two couplings not listed in
Context, both removed rather than ported:[data-radix-popper-content-wrapper]to drive its repositioning transition, a stylesheet styled that wrapper, and a test asserted against it. Base UI'sPositioneris an element the application owns, so all three bindings disappear. This is the migration's clearest structural gain.Popover,Tooltip,DropdownMenu, andMenubareach consumed--radix-*-content-transform-origin, six occurrences. Base UI exposes--transform-originon the positioner.Baseline
pnpm check:frontendpasses at3b678ee: oxfmt,tsc -b, oxlint type-aware, build, andvitest run --coverageat 90.79% statements, 81.59% branches, 92.17% functions, 92.08% lines.dist/assets/index-*.jsdist/assets/index-*.cssDependency footprint:
radix-ui@1.6.7resolved to 60@radix-ui/*packages, about 5.4 MB installed. The application ships as a single JavaScript chunk dominated by the editor and syntax-highlighting stack, and Radix's share of it was not isolated.Rejected alternatives
[data-radix-popper-content-wrapper]coupling indefinitely. The migration cost is accepted against that.Scope delivered
@base-ui/react;Checkbox,ToggleGroup, andTogglewere deleted rather than migrated.sonnerreplaced by Base UI Toast.src/lib/toast.tsowns the toast manager and thenotify*API so callers outside the React tree keep working; the component owns provider, viewport, and presentation.components.json, which relocatedcntosrc/lib/utils.ts. The wrappers stay hand-owned.src/AGENTS.md.How the open questions resolved
The decision was taken on the measured surface rather than on a prototype, so these were carried into implementation as risks:
VirtualElementpreserves the selection-following behavior that fix: move the context popup onto its selection when the selection changes #174 introduced, with no new mechanism needed.Toolbar.ButtondefaultingfocusableWhenDisabledtotruewas overridden in the wrapper, keeping disabled controls out of the roving sequence so vertical row movement and horizontal roving agree on what is reachable.onOpenChange, by cancelling anoutside-presschange rather than a pointer-down event.Escape,Tab, or the vertical arrows.decorative, but that was a template default rather than a Leafdown requirement, and all four call sites separate real content groups. No override was added.Verification
#182 records the evidence.
pnpm check:frontendpasses, and focused tests prove dialog dismissal against the titlebar drag region, command menubar submenu and close behavior, the editor context popup's anchoring, focus ownership, row traversal and dismissal, and that everynotify*call reaches the toast manager.The three behavior-sensitive surfaces were also exercised by hand in
pnpm tauri devon Windows 11: dialog dismissal against real window drag and window controls, the command menubar keyboard walk, and the editor context popup in the real WebView. Linux and macOS remain unverified, as they are not the current polish target.Documentation updated
docs/decisions.mdcarries the decision underTechnical Decisions.src/AGENTS.mdrecords Base UI as the preferred primitive library, the relocatedcnpath, and the file naming rule.docs/patterns.mdno longer describes focus and traversal behavior in Radix's terms.