useOutsideEvent is a hook that subscribes to an event at the document level and calls the handler only if the event occurred outside of the provided element (ref). Perfect for closing modals/dropdowns on outside click, handling “outside” mousedown/touchstart, etc.
function useOutsideEvent<
ElementType extends HTMLElement,
EventType extends UseOutsideEventType,
>(
ref: RefObject<ElementType | null>,
eventType: EventType,
handler: UseOutsideEventHandler<EventType>,
): void;-
Parameters
ref— a ref to the target DOM element for which you want to detect outside events.eventType— a DOM event type, e.g.'click' | 'mousedown' | 'pointerdown' | 'touchstart', etc.handler— a callback fired only when the event happened outside ofref.current.
-
Returns:
void.
const ref = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(false);
useOutsideEvent(ref, 'click', () => setOpen(false));
return (
<div>
<button onClick={() => setOpen((v) => !v)}>Toggle</button>
{open && (
<div ref={ref} role="menu"> ... </div>
)}
</div>
);// Fires at press time, before `click`
useOutsideEvent(ref, 'mousedown', onOutsidePress);
// On touch devices — similar
useOutsideEvent(ref, 'touchstart', onOutsideTouch);useOutsideEvent(ref, 'pointerdown', onOutside);
useOutsideEvent(ref, 'keydown', (e) => {
if (e.key === 'Escape') onOutside(e);
});-
What counts as “outside”
- An event is considered outside if
!element.contains(event.target as Element). - For Shadow DOM specifics, consider
event.composedPath()if you need cross‑root containment checks.
- An event is considered outside if
-
Subscription / cleanup
- Subscribes on
documentwhen mounted / whenref.currentis set. - Cleans up on unmount and when
eventTypechanges.
- Subscribes on
-
Fresh handler
- The latest
handlerversion is invoked viauseLiveRef(no resubscribe when the handler changes).
- The latest
-
Dependencies
- Resubscription occurs on
eventTypechange.handlerchanges do not trigger resubscription.
- Resubscription occurs on
-
SSR‑safe
- Side effects run only in the browser (
useEffect/useRefEffectscope).
- Side effects run only in the browser (
- Closing popovers/dropdowns/menus with outside interactions.
- Blocking interactions outside a specific area.
- Canceling gestures/actions when a click occurs outside the component.
- If the overlay/content is rendered via a portal that logically belongs to the widget but lives outside of the element’s DOM tree,
containswill returnfalse— the event will be treated as “outside”. In such cases, use a shared containerrefor custom logic. - If you need to catch events only inside the element — attach regular handlers to the element itself.
-
ref.currentisnull- No subscription occurs until the element is mounted. Ensure the
refis attached.
- No subscription occurs until the element is mounted. Ensure the
-
Unsuitable event type
clickfires later thanmousedown/pointerdown. For immediate reaction, prefer a “down” event.
-
Stopping propagation
- If a handler inside the element calls
event.stopPropagation(), the event will not reachdocument; your outside handler won’t run.
- If a handler inside the element calls
-
Shadow DOM assumptions
containsacross different shadow roots may not work as expected. Useevent.composedPath()if necessary.
Exported types
-
UseOutsideEventHandler<EventType extends UseOutsideEventType = UseOutsideEventType>- Callback fired when the specified DOM event occurs:
(event: GlobalEventHandlersEventMap[EventType]) => void.
- Callback fired when the specified DOM event occurs:
-
UseOutsideEventType- A union of all standard DOM event names (
keyof GlobalEventHandlersEventMap). - Restricts which events can be used with
useOutsideEvent.
- A union of all standard DOM event names (