@kitsunechaos/use-outside-click
v1.0.2
Published
A tiny, modern React hook to detect clicks outside an element. SSR-safe, TypeScript-native, zero dependencies. Perfect for modals, dropdowns, popovers, tooltips, context menus, sidebars, and more.
Downloads
300
Maintainers
Keywords
Readme
use-outside-click
The modern React hook for detecting clicks outside an element.
Tiny · TypeScript-native · SSR-safe · Zero dependencies · Touch + Mouse
Why use-outside-click?
Every React developer who builds a dropdown, modal, popover, tooltip, context menu, or sidebar needs this util. Yet the existing solutions are:
| Package | Problem |
|---|---|
| react-outside-click-handler | Class-based wrapper, ~6 kb, 4 years old |
| react-onclickoutside | HOC pattern, unmaintained |
| Stack Overflow snippets | No TypeScript, no SSR guard, no cleanup |
use-outside-click is the hook-first, TypeScript-native, zero-dependency solution you've been looking for — shipping in ~300 bytes gzipped.
Features
- Drop-in simple — one hook call, any element
- TypeScript native — full types, no
@types/package needed - Multiple refs — watch several elements at once
- SSR safe — works with Next.js, Remix, Astro out of the box
- Touch + mouse — mobile-first by default
- Capture phase — works even with
stopPropagation - Zero dependencies — React peer dep only
- Inline handler stable — no infinite re-subscription loops
enabledtoggle — zero overhead when disabled- Auto cleanup — removes listeners on unmount
Installation
npm install @kitsunechaos/use-outside-click
# or
yarn add @kitsunechaos/use-outside-click
# or
pnpm add @kitsunechaos/use-outside-clickPeer dependencies: React ≥ 16.8 (hooks era)
Quick Start
import { useRef, useState } from 'react';
import { useOutsideClick } from '@kitsunechaos/use-outside-click';
function Dropdown() {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useOutsideClick(ref, () => setOpen(false));
return (
<div ref={ref}>
<button onClick={() => setOpen(true)}>Open</button>
{open && <ul><li>Option 1</li><li>Option 2</li></ul>}
</div>
);
}That's it. One import, one hook call.
Examples
Modal
import { useRef, useState } from 'react';
import { useOutsideClick } from '@kitsunechaos/use-outside-click';
function Modal({ onClose }: { onClose: () => void }) {
const modalRef = useRef<HTMLDivElement>(null);
useOutsideClick(modalRef, onClose);
return (
<div className="overlay">
<div ref={modalRef} className="modal">
<h2>Hello from Modal</h2>
<button onClick={onClose}>Close</button>
</div>
</div>
);
}Tooltip
function Tooltip() {
const [visible, setVisible] = useState(false);
const ref = useRef<HTMLSpanElement>(null);
useOutsideClick(ref, () => setVisible(false));
return (
<span ref={ref}>
<button onClick={() => setVisible(true)}>Info</button>
{visible && <div className="tooltip">More info here!</div>}
</span>
);
}Multiple Refs (e.g. Popover + Trigger Button)
Useful when your trigger button and popover are separate DOM nodes and you want clicks on either to be considered "inside".
function Popover() {
const [open, setOpen] = useState(false);
const triggerRef = useRef<HTMLButtonElement>(null);
const popoverRef = useRef<HTMLDivElement>(null);
// Pass an array — clicks inside EITHER ref are ignored
useOutsideClick([triggerRef, popoverRef], () => setOpen(false));
return (
<>
<button ref={triggerRef} onClick={() => setOpen((v) => !v)}>
Toggle Popover
</button>
{open && (
<div ref={popoverRef} className="popover">
Popover content here
</div>
)}
</>
);
}Conditionally Enabled
Disable the listener without unmounting the component (zero event listener overhead):
useOutsideClick(ref, handleClose, { enabled: isOpen });Custom Events
By default the hook listens to mousedown and touchstart.
Override with any DocumentEventMap key:
// Only fire on mousedown (not touch)
useOutsideClick(ref, handleClose, { events: ['mousedown'] });
// Fire on pointerdown instead
useOutsideClick(ref, handleClose, { events: ['pointerdown'] });API
useOutsideClick(
target: Ref | Ref[],
handler: (event: MouseEvent | TouchEvent) => void,
options?: UseOutsideClickOptions
): voidParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| target | RefObject<Element> | RefObject<Element>[] | ✅ | The element(s) to watch. Pass an array to watch multiple. |
| handler | (event) => void | ✅ | Callback fired when an outside click/touch is detected. |
| options | UseOutsideClickOptions | — | Optional configuration (see below). |
Options
| Option | Type | Default | Description |
|---|---|---|---|
| enabled | boolean | true | Disable the listener entirely. No event listener is attached when false. |
| events | (keyof DocumentEventMap)[] | ['mousedown', 'touchstart'] | DOM events to subscribe to. |
SSR / Next.js
use-outside-click is SSR-safe out of the box — it guards every document access behind a typeof document !== 'undefined' check.
No special config needed for Next.js, Remix, Astro, or any other SSR framework.
// app/components/Dropdown.tsx ← Next.js App Router
'use client'; // only this directive needed
import { useOutsideClick } from '@kitsunechaos/use-outside-click';How It Works
- Listens on
document(not the element) using the capture phase — this catches events even whene.stopPropagation()is called inside the target. - Checks whether
event.targetis contained within any of your refs usingNode.contains(). - The
handleris tracked with a ref — so inline functions (common in React) never cause the effect to re-subscribe. - All listeners are automatically removed on unmount.
Bundle Size
| Format | Size | |---|---| | Minified | ~500 B | | Gzipped | ~300 B | | Brotli | ~270 B |
Zero runtime dependencies. React is a peer dep.
TypeScript
Full TypeScript support is built-in. No separate @types/ package required.
import type { UseOutsideClickOptions, OutsideClickTarget } from '@kitsunechaos/use-outside-click';Browser Support
All modern browsers + IE 11 (with polyfills for TouchEvent if needed).
Uses addEventListener with { capture, passive } options.
Contributing
Contributions are welcome! Please open an issue or pull request.
# Clone and install
git clone https://github.com/use-outside-click/use-outside-click.git
cd use-outside-click
npm install
# Run tests
npm test
# Build
npm run buildLicense
MIT © use-outside-click contributors
If this saved you time, please star the repository and share it — it helps others find it!
