@kaviresh01/use-clickaway
v1.0.0
Published
A zero-dependency React hook that detects clicks outside elements. Handles portals, Shadow DOM, and SSR.
Downloads
20
Maintainers
Readme
use-clickaway
A tiny, zero-dependency React hook that fires a callback when a click or touch occurs outside a given element. Handles React Portals, Shadow DOM, and SSR correctly.
Why another outside-click hook?
Most existing packages:
- ❌ Break with React Portals (modals, dropdowns rendered via
createPortal) - ❌ Don't handle Shadow DOM (web components, browser extensions)
- ❌ Re-attach event listeners on every render
- ❌ Ignore touch events for mobile
- ❌ Have unnecessary dependencies
@kaviresh01/use-clickaway fixes all of this.
Install
npm install @kaviresh01/use-clickaway
# or
yarn add @kaviresh01/use-clickaway
# or
pnpm add @kaviresh01/use-clickawayBasic Usage
import { useRef, useState } from "react";
import { useClickaway } from "@kaviresh01/use-clickaway";
function Dropdown() {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useClickaway(ref, () => setOpen(false));
return (
<div ref={ref}>
<button onClick={() => setOpen(true)}>Open</button>
{open && <ul><li>Item 1</li><li>Item 2</li></ul>}
</div>
);
}API
useClickaway(ref, handler, options?)Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| ref | RefObject<Element> | RefObject<Element>[] | ✅ | One or more refs to watch |
| handler | (event: MouseEvent \| TouchEvent) => void | ✅ | Called on outside click/touch |
| options | Options | — | See below |
Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enabled | boolean | true | Toggle the listener on/off without unmounting |
| detectTouch | boolean | true | Also listen for touchstart (mobile) |
| ignoreRefs | RefObject<Element>[] | [] | Extra refs that should NOT trigger the handler |
Examples
Multiple refs (e.g. trigger button + dropdown panel)
const buttonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLUListElement>(null);
useClickaway([buttonRef, menuRef], () => setOpen(false));Conditional / toggled
useClickaway(ref, () => setOpen(false), { enabled: open });Ignore a specific element
const tooltipRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLButtonElement>(null);
// Clicking the trigger won't close the tooltip
useClickaway(tooltipRef, () => setVisible(false), {
ignoreRefs: [triggerRef],
});Works with React Portals
// Even if your modal is rendered via createPortal, this still works.
const modalRef = useRef<HTMLDivElement>(null);
useClickaway(modalRef, () => setModalOpen(false));How it works
Instead of checking event.target, this hook uses event.composedPath()[0] — the deepest element in the event's composed path. This correctly resolves clicks that originate inside:
- React Portals — rendered outside the DOM tree of the ref
- Shadow DOM — used by web components and browser extensions
The handler is stored in a stable ref, so the event listener is never re-attached on re-renders — only when enabled or detectTouch changes.
License
MIT
