Tooltip's focus={{ enabled: true }} has no effect — FloatingOverrideContext is written but never consumed
Current Behavior
Tooltip's focus={{ enabled: true }} prop has no effect on web: focusing the trigger (via Tab, or programmatically) never opens the tooltip, even though the prop is accepted and documented in TooltipProps. Hover still opens the tooltip correctly.
Reproduced against a real Chromium browser (not just a test environment) — confirmed the trigger element itself does receive real DOM focus and matches :focus-visible, but the Tooltip's data-state stays "closed" and aria-expanded stays "false".
Expected Behavior
Setting focus={{ enabled: true }} (or focusing the trigger by default, matching typical tooltip accessibility conventions) should open the tooltip on keyboard focus, the same way hover does.
Root Cause (traced through source, not just compiled output)
Tooltip (@tamagui/tooltip/src/Tooltip.tsx) builds an interaction-aware floating context via useFloatingContext() from @tamagui/popover — this wires up useFocus() (from @tamagui/floating) using the focus prop — and provides it down through FloatingOverrideContext:
const floatingContext = useFloatingContext({
...
focus,
...
})
const content = (
<FloatingOverrideContext.Provider value={floatingContext}>
<Popper ...>
...
</Popper>
</FloatingOverrideContext.Provider>
)But Popper (@tamagui/popper/src/Popper.tsx) builds its own, separate, purely-positional useFloating() instance (line ~419) — it never reads FloatingOverrideContext via useContext() anywhere in the file — and then explicitly resets the context to null for its own children:
// line ~513
{/* reset FloatingOverrideContext so it doesn't leak into nested Poppers —
each Popper consumes the override for its own useFloating, children
should not inherit it (e.g. a Menu inside a Tooltip's tree) */}
<FloatingOverrideContext.Provider value={null}>
{children}
</FloatingOverrideContext.Provider>The comment describes intended behavior ("each Popper consumes the override for its own useFloating"), but the consuming half doesn't exist in the code — FloatingOverrideContext is only ever written to (reset), never read. As a result, PopperAnchor's getReferenceProps() — which is what attaches onFocus/onBlur handlers to the actual trigger DOM node — always comes from the interaction-less useFloating() instance Popper builds itself, never from the focus-aware context Tooltip constructed.
Confirmed directly by instrumenting useFocus's onFocus callback (@tamagui/floating/src/interactions/useFocus.ts) with a console.log: it never fires at all on Tab, in a real Chromium browser via Playwright — not gated by an internal condition (e.g. visibleOnly/matchesFocusVisible), simply never invoked, because the handler is never attached to the DOM element in the first place.
Hover continues to work because it's driven by separate, hand-wired onMouseEnter/onOpenChange plumbing at the PopoverTrigger/PopoverContent level, unrelated to this broken interaction-props path.
Repro
import { TamaguiProvider, Tooltip } from 'tamagui'
import config from './tamagui.config'
export default function App() {
return (
<TamaguiProvider config={config}>
<Tooltip focus={{ enabled: true }}>
<Tooltip.Trigger asChild>
<button>Tab to me</button>
</Tooltip.Trigger>
<Tooltip.Content>Should open on focus</Tooltip.Content>
</Tooltip>
</TamaguiProvider>
)
}Tab to the button (or click it, then .focus() programmatically). Expected: tooltip opens. Actual: aria-expanded stays false, no role="tooltip" element renders. Hovering the same trigger opens it correctly.
Tamagui Version
2.6.0(latest published version at time of writing — not reproduced against an older/stale install)
Platform
Web
Source: tamagui/tamagui