Skip to content

Tooltip.Trigger does not close on right/middle click (pointerdown close missing from Radix port) #2099

Description

@MathiasWP

Describe the bug

Tooltip.Trigger closes an open tooltip on left click, but not on right click (or middle click), so a context menu raised from a tooltip trigger opens on top of a tooltip that stays put.

This looks like a one-line omission in the Radix port rather than a design decision. Radix's TooltipTrigger closes on pointerdown for any button:

// radix-ui/primitives — packages/react/tooltip/src/tooltip.tsx
onPointerDown={composeEventHandlers(props.onPointerDown, () => {
  if (context.open) context.onClose();
  isPointerDownRef.current = true;
  document.addEventListener('pointerup', handlePointerUp, { once: true });
})}

TooltipTriggerState on main has the same handler with the same isPointerDown + pointerup bookkeeping, but without the close:

// packages/bits-ui/src/lib/bits/tooltip/tooltip.svelte.ts
#onpointerdown: PointerEventHandler<HTMLElement> = () => {
    if (this.#isDisabled()) return;
    this.#isPointerDown.current = true;

    this.domContext.getDocument().addEventListener(
        "pointerup",
        () => {
            this.handlePointerUp();
        },
        { once: true }
    );
};

#onclick still calls root.handleClose(), which is why left click works and the other buttons don't.

Two consequences beyond the visual overlap:

  1. pointerdown fires before contextmenu, so closing there is what lets a trigger's own context menu open cleanly. Working around it from userland means listening for contextmenu on the trigger, which is later and does not cover middle click.
  2. handleClose() also stops the pending open timer. A right click during the hover delay currently leaves the timer running, so the tooltip appears after the context menu has opened.

Suggested fix — mirror Radix:

#onpointerdown: PointerEventHandler<HTMLElement> = () => {
    if (this.#isDisabled()) return;
    const root = this.#getRoot();
    if (root?.opts.open.current) root.handleClose();
    this.#isPointerDown.current = true;
    // …unchanged
};

Happy to open a PR if you'd like it. Worth deciding whether disableCloseOnTriggerClick should gate this too — Radix has no equivalent, and I'd lean towards it gating both so the prop keeps meaning "the trigger doesn't dismiss".

Reproduction

No scaffold needed — the docs demo reproduces it: https://bits-ui.com/docs/components/tooltip

  1. Hover the tooltip trigger in the first demo and wait for the tooltip.
  2. Right-click the trigger (or middle-click it).

Expected: the tooltip closes, as it does on left click.
Actual: it stays open, and the browser's native context menu opens over it.

For the pending-timer variant: hover the trigger and right-click it before the delay elapses — the tooltip appears afterwards, over the open menu.

System Info

  System:
    OS: macOS 26.5.2
    CPU: (10) arm64 Apple M2 Pro
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 24.15.0
    npm: 11.12.1
    pnpm: 11.16.0
  Browsers:
    Chrome: 151.0.7922.76
    Firefox: 142.0.1
    Safari: 26.5.2
  npmPackages:
    @sveltejs/kit: 2.70.2 => 2.70.2
    bits-ui: 2.18.1 => 2.18.1
    svelte: 5.56.8 => 5.56.8

Severity

annoyance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions