A tiny, zero-dependency, TypeScript-first React hook for detecting interactions outside one or more elements.
npm install outside-click-hookpnpm add outside-click-hookyarn add outside-click-hookimport { useClickOutside } from "outside-click-hook";
export function Menu({ onClose }: { onClose: () => void }) {
const ref = useClickOutside<HTMLDivElement>(() => {
onClose();
});
return <div ref={ref}>Menu content</div>;
}const ref = useClickOutside(() => {
console.log("Outside click");
});const ref = useClickOutside(onClose, {
enabled: true,
event: "pointerdown",
});const modalRef = useRef<HTMLDivElement>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
useClickOutside([modalRef, dropdownRef], onClose);| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Enables or disables all listeners. |
event |
"click" | "mousedown" | "mouseup" | "pointerdown" |
"pointerdown" |
Document event used for outside interactions. |
escapeKey |
boolean |
true |
Calls the handler when Escape is pressed. |
ignoreScrollbar |
boolean |
true |
Ignores clicks on the browser scrollbar. |
ignore |
Array<React.RefObject<HTMLElement | null>> |
[] |
Refs that should not trigger the outside handler. |
import { useClickOutside } from "outside-click-hook";
function Modal({ onClose }: { onClose: () => void }) {
const ref = useClickOutside<HTMLDivElement>(onClose, { escapeKey: true });
return (
<div role="dialog" aria-modal="true" ref={ref}>
Modal content
</div>
);
}const buttonRef = useRef<HTMLButtonElement>(null);
const menuRef = useClickOutside<HTMLDivElement>(onClose, {
ignore: [buttonRef],
});useClickOutside([triggerRef, popoverRef], onClose, {
enabled: open,
event: "pointerdown",
});More complete examples live in examples.
The package is written in TypeScript and ships generated declaration files. Pass the element type as a generic when using the returned ref:
const ref = useClickOutside<HTMLButtonElement>(onOutside);The handler receives the native event that triggered it:
const ref = useClickOutside<HTMLDivElement>((event) => {
event.type;
});pointerdown is the default because it works well across mouse, pen, and touch input in modern browsers. You can switch to click, mousedown, or mouseup when a specific interaction model needs it.
The hook is SSR safe: it does not access window or document during render.
Yes. React is a peer dependency and the supported range supports React 18 and newer.
Yes. The listener runs on document in the capture phase and uses composedPath() when available, which helps with portals and shadow DOM boundaries.
No. The package has zero runtime dependencies.
Escape is a common close gesture for modals, popovers, context menus, and dropdowns. Disable it with escapeKey: false.
- Install dependencies with
npm install. - Run
npm run lint. - Run
npm run typecheck. - Run
npm test. - Run
npm run build.
Please add or update tests for behavior changes.
MIT (c) outside-click-hook contributors