useClickOutside
useClickOutside attaches a document-level listener that fires only when the click lands outside the referenced element(s). It is the building block behind dismissable popovers, menus, and detail panels — anything that should close when the user clicks “somewhere else”.
Live preview
Import
import { useClickOutside } from 'entangle-ui';Signature
function useClickOutside<T extends HTMLElement = HTMLElement>( ref: RefObject<T | null> | Array<RefObject<HTMLElement | null>>, handler: (event: MouseEvent) => void, options?: UseClickOutsideOptions): void;
interface UseClickOutsideOptions { enabled?: boolean; event?: 'mousedown' | 'click' | 'pointerdown';}Usage
function Popover({ onClose }: { onClose: () => void }) { const ref = useRef<HTMLDivElement>(null); useClickOutside(ref, onClose);
return <div ref={ref}>...</div>;}Basic
Basic
Trigger + popover pair
Pass an array of refs when the “inside” group spans multiple elements — for example a trigger button outside the popover whose click should not close it.
Multiple refs
const triggerRef = useRef<HTMLButtonElement>(null);const popoverRef = useRef<HTMLDivElement>(null);
useClickOutside([triggerRef, popoverRef], () => setOpen(false));API
| Prop | Type | Default | Description |
|---|---|---|---|
ref * | RefObject<HTMLElement | null> | RefObject<HTMLElement | null>[] | — | Single ref or an array of refs. When an array, the handler fires only when the click is outside ALL refs. |
handler * | (event: MouseEvent) => void | — | Called when an outside click is detected. The latest handler is always invoked — no need to memoize. |
options.enabled | boolean | true | When false, the listener is detached. Toggle this to disable behavior without unmounting. |
options.event | 'mousedown' | 'click' | 'pointerdown' | 'mousedown' | Which event to listen for. `mousedown` fires before `click`, which prevents a click that closes a popover from also activating something underneath. |
Common pitfalls
- Triggers outside the ref: A button that opens a popover is not inside the popover. Pass both refs as an array, otherwise clicking the trigger will immediately close the popover that just opened.
- Event timing: Listening on
clickinstead ofmousedownmeans the dismiss happens after the click event has already activated other handlers (selecting text, focusing inputs, navigating links). Stick tomousedownunless you have a specific reason. - Portals: Elements rendered into a portal are not descendants of the ref’s DOM subtree. Pass a ref to the portal content as well, or close on outside click is going to fire when the user interacts with the popover body.
- SSR: The hook is a no-op on the server. The listener attaches in
useEffect, which only runs in the browser.