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
In React 17, opening a Popover for the first time via mouse click causes the positioner to animate from the viewport top-left (0, 0) to its target position, instead of appearing in place next to the trigger. 100% reproducible on the first open after mount
React 19 is not affected. @base-ui/react 1.4.1 + React 17 is not affected
Expected behavior
The positioner's initial position change (from translate(0,0) to translate(x,y)) should not trigger a CSS transition. The popover should appear directly beside the trigger.
Which assistive tech are you using (if applicable)?
N/A.
Additional context
Base UI guards the positioner's initial-position transition with an inline style: { transition: 'none' } that only lives while transitionStatus === 'starting' — one animation frame. The position itself is computed by @floating-ui/react-dom's useFloating as a microtask (computePosition().then(...) → flushSync(setData)). Per the HTML spec, microtasks run before requestAnimationFrame, so as long as update() is called inside a layout effect, the position settles before transitionStatus is cleared and no transition fires.
In React 17, update() is not called inside the first layout effect. The floatingElement has to cross from PopoverStore to FloatingRootStore and then reach useFloating. Base UI switches its store implementation by React version: React 17 goes through the use-sync-external-store/shim polyfill, which attaches its store subscription in useEffect (runs after paint) rather than useLayoutEffect. As a result, the FloatingRootStore.floatingElement update that happens during the first commit cycle is not propagated to useFloating in the same cycle — the subscription isn't installed yet. useFloating only finds out in useEffect, which runs after the rAF that clears transitionStatus has already fired. By the time computePosition resolves and the position changes from translate(0,0) to translate(x,y), the inline transition: none has already been removed, so the CSS transition fires and the popover slides in from the top-left corner.
React 19 is unaffected because the native useSyncExternalStore has tearing protection — store changes during render are detected immediately, so update() is called inside the first layout effect, the microtask runs before rAF, and the position settles while transition: none is still applied.
Bug report
Current behavior
In React 17, opening a
Popoverfor the first time via mouse click causes the positioner to animate from the viewport top-left(0, 0)to its target position, instead of appearing in place next to the trigger. 100% reproducible on the first open after mountReact 19 is not affected. @base-ui/react 1.4.1 + React 17 is not affected
Expected behavior
The positioner's initial position change (from
translate(0,0)totranslate(x,y)) should not trigger a CSS transition. The popover should appear directly beside the trigger.Reproducible example
Screen.Recording.2026-07-08.at.19.04.08.mov
CodeSandbox
Base UI version
1.6.0
Which browser are you using?
Chrome
Which OS are you using?
Mac OS
Which assistive tech are you using (if applicable)?
N/A.
Additional context
Base UI guards the positioner's initial-position transition with an inline
style: { transition: 'none' }that only lives whiletransitionStatus === 'starting'— one animation frame. The position itself is computed by@floating-ui/react-dom'suseFloatingas a microtask (computePosition().then(...)→flushSync(setData)). Per the HTML spec, microtasks run beforerequestAnimationFrame, so as long asupdate()is called inside a layout effect, the position settles beforetransitionStatusis cleared and no transition fires.In React 17,
update()is not called inside the first layout effect. ThefloatingElementhas to cross fromPopoverStoretoFloatingRootStoreand then reachuseFloating. Base UI switches its store implementation by React version: React 17 goes through theuse-sync-external-store/shimpolyfill, which attaches its store subscription inuseEffect(runs after paint) rather thanuseLayoutEffect. As a result, theFloatingRootStore.floatingElementupdate that happens during the first commit cycle is not propagated touseFloatingin the same cycle — the subscription isn't installed yet.useFloatingonly finds out inuseEffect, which runs after the rAF that clearstransitionStatushas already fired. By the timecomputePositionresolves and the position changes fromtranslate(0,0)totranslate(x,y), the inlinetransition: nonehas already been removed, so the CSS transition fires and the popover slides in from the top-left corner.React 19 is unaffected because the native
useSyncExternalStorehas tearing protection — store changes during render are detected immediately, soupdate()is called inside the first layout effect, the microtask runs before rAF, and the position settles whiletransition: noneis still applied.