A useClickOutside Hook runs a callback when the user presses outside a given element. It is the standard way to close a menu, popover, or dropdown when the pointer leaves it, and it works with mouse, touch, and pen. The callback runs on the press rather than on release, so the menu closes at the moment the user interacts outside it.
Build the Hook
The Hook takes a ref and a callback, then listens on the document so presses anywhere can be checked against the element.
import { useEffect } from "react";
function useClickOutside(ref, onOutside) {
useEffect(() => {
function handlePointerDown(event) {
if (ref.current && !ref.current.contains(event.target)) onOutside();
}
document.addEventListener("pointerdown", handlePointerDown);
return () => document.removeEventListener("pointerdown", handlePointerDown);
}, [ref, onOutside]);
}ref.current is the element to protect. The listener stays attached even while the element is unmounted, such as a closed menu, so the ref.current check skips the callback instead of throwing when there is nothing to compare against. contains returns true when the press landed on the element or any descendant, so only genuine outside presses reach onOutside.
The cleanup removes the listener. Keep onOutside stable, or the Effect re-subscribes on every render. Passing an inline arrow changes the callback on each render and re-attaches the listener each time.
Use it for a dropdown
Attach the ref to the panel that appears, and call the Hook from the component that owns the open state.
import { useCallback, useRef, useState } from "react";
function Dropdown() {
const [open, setOpen] = useState(false);
const menuRef = useRef(null);
const close = useCallback(() => setOpen(false), []);
useClickOutside(menuRef, close);
return (
<div>
<button onClick={() => setOpen(!open)}>Menu</button>
{open && <div ref={menuRef}>Options</div>}
</div>
);
}close keeps the same identity across renders, so the Effect inside useClickOutside subscribes once instead of removing and re-adding the listener on every render. Clicking the button toggles the menu. A press anywhere outside menuRef runs close, so the menu closes.
A press inside the menu does nothing, and a press on the button toggles it as expected. The ref points at the open menu only while it exists, so the check is skipped when the menu is closed. useClickOutside closes it without adding a click handler to every other element on the page.
Keep the interaction accessible
Outside click alone does not cover keyboard users or screen readers. pointerdown fires before a click and works for touch and pen too, which is why the Hook uses it.
- Also close on Escape and return focus to the trigger.
- For a true modal, prefer a native dialog element or proper ARIA role.
- Keep the callback stable, or the Effect re-subscribes on every render.
A dismissible menu still needs a visible trigger, a focus target, and a way to reopen it. Outside click is one piece of that, not the whole pattern.
Why pointerdown instead of click
pointerdown fires as soon as the pointer presses, before focus or layout changes can move the target. It also covers touch and pen, where a click event may not behave the same way. For closing menus, pressing is the right moment because the user has already started to interact outside.
See how to create a custom Hook for the extraction steps, and how to build an accessible React modal with portals for the fuller dialog pattern.
Rune AI
Key Insights
- Attach the ref to the element that should stay open.
- Listen for pointerdown on document and check ref.current.contains(event.target).
- Remove the listener in the Effect cleanup.
- Keep the callback stable to avoid re-subscribing every render.
- Pair outside click with Escape and focus return.
Frequently Asked Questions
Why listen for pointerdown instead of click?
What does contains return when the target is the element itself?
Should outside click also close on the Escape key?
Conclusion
A useClickOutside Hook attaches a document-level pointerdown listener and runs a callback when the press lands outside the given ref. Combine it with Escape handling and focus management to keep menus and popovers accessible.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.