useOverlay
Hooks

useOverlay

Provides the anchor and panel refs, controllable open state, outside-click and Escape dismissal, and the aria pair on the trigger.

Signature

useOverlay returns anchor and panel refs, controllable open state, dismissal handlers, and trigger accessibility attributes.

SignatureTypeScript
function useOverlay<A extends HTMLElement = HTMLElement>(options?: {
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean) => void;
  /** `aria-haspopup` on the trigger. `false` for a tooltip, which is not a popup. */
  haspopup?: "menu" | "listbox" | "dialog" | "tree" | "grid" | false;
  /** Outside pointerdown and Escape close it. Default true. */
  dismissable?: boolean;
  disabled?: boolean;
  /** Leaves `onClick` off `triggerProps` — for hover- or focus-driven overlays. */
  manual?: boolean;
}): {
  open: boolean;
  setOpen: (open: boolean) => void;
  toggle: () => void;
  close: () => void;
  anchorRef: { current: A | null };
  panelRef: { current: HTMLDivElement | null };
  /** Spread onto the trigger element. */
  triggerProps: { ref; onClick?; "aria-haspopup"?; "aria-expanded"? };
  /** Spread into `Popover` — placement, offset and matchWidth stay yours. */
  popoverProps: { open; anchorRef; panelRef };
};

Usage

Spread triggerProps onto the trigger element and popoverProps into Popover.

ExampleTSX
import type { ReactNode } from "react";
import { useOverlay, Popover, Surface } from "@solarius/onset";

function FilterMenu({ children }: { children: ReactNode }) {
  const { open, triggerProps, popoverProps } = useOverlay<HTMLButtonElement>({
    haspopup: "menu",
  });

  return (
    <>
      <button {...triggerProps} data-state={open ? "open" : "closed"}>
        Filters
      </button>
      <Popover {...popoverProps} placement="bottom-end" offset={6}>
        <Surface elevation="overlay">{children}</Surface>
      </Popover>
    </>
  );
}

Pass manual for an overlay that is not opened by a click. HoverCard and Tooltip both do: they use the refs, state, and aria pair but supply their own pointer handlers. Tooltip additionally passes haspopup: false, because a tooltip is not a popup, and dismissable: false, because it appears and disappears with the pointer rather than with a click elsewhere.

Closing side effects

Use onOpenChange to reset temporary state when an overlay closes. This callback receives state changes from each close action.

ExampleTSX
const { open, setOpen, triggerProps, popoverProps } = useOverlay({
  haspopup: "dialog",
  onOpenChange: (next) => {
    if (!next) setDraft(null);
  },
});