Every React developer has written this code at least once:
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
setOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [ref]);
You paste it from Stack Overflow. It works. You move on. Then you write it again next week for the modal. Then again, for the dropdown. Then again, for the tooltip.
Sound familiar?
That's exactly why I built @kitsunechaos/use-outside-click.
The Problem
Detecting a click outside an element is one of the most common UI patterns in React:
- Closing a dropdown when you click elsewhere
- Dismissing a modal on backdrop click
- Collapsing a popover, tooltip, or color picker
- Hiding a mobile navigation drawer
Yet, every time, we write the same boilerplate — or worse, reach for a 6KB library that wraps it in a class component HOC from 2018.
There had to be a better way.
Introducing @kitsunechaos/use-outside-click
A dead-simple React hook that detects clicks outside any element. That's it. No fluff.
npm install @kitsunechaos/use-outside-click
Usage
import { useRef } from "react";
import useOutsideClick from "@kitsunechaos/use-outside-click";
function Dropdown() {
const ref = useRef(null);
const [open, setOpen] = useState(false);
useOutsideClick(ref, () => setOpen(false));
return (
<div ref={ref}>
<button onClick={() => setOpen(true)}>Open</button>
{open && <ul>{/* dropdown items */}</ul>}
</div>
);
}
One import. One ref. One callback. Done.
What Makes It Different
✅ Zero Dependencies
No bloat. No peer dependency warnings. Nothing extra in your node_modules.
✅ Fully Typed
Written in TypeScript from the ground up. Full type inference — no @types/ package needed.
useOutsideClick(ref: RefObject<HTMLElement>, callback: () => void): void
✅ SSR Safe
Works seamlessly with Next.js, Remix, and any server-side rendering setup. No window is not defined errors.
✅ Touch + Mouse Support
Handles both mousedown and touchstart events — works perfectly on mobile devices too.
✅ Tiny Bundle Size
Under 400 bytes gzipped. Your users will never notice it's there.
✅ Supports Multiple Refs
Need to track outside clicks across multiple elements? Supported out of the box.
Real World Example — Dropdown Menu
import { useRef, useState } from "react";
import useOutsideClick from "@kitsunechaos/use-outside-click";
export function DropdownMenu() {
const ref = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
useOutsideClick(ref, () => setIsOpen(false));
return (
<div ref={ref} style={{ position: "relative", display: "inline-block" }}>
<button onClick={() => setIsOpen((prev) => !prev)}>
Options ▾
</button>
{isOpen && (
<ul style={{ position: "absolute", background: "white", border: "1px solid #eee" }}>
<li>Edit</li>
<li>Duplicate</li>
<li>Delete</li>
</ul>
)}
</div>
);
}
No more cleanup logic. No more forgotten removeEventListener. Just clean, readable code.
Before vs After
Before — raw boilerplate every time:
// 10+ lines, every single component
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
onClose();
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
After — one line:
useOutsideClick(ref, onClose);
Installation
# npm
npm install @kitsunechaos/use-outside-click
# yarn
yarn add @kitsunechaos/use-outside-click
# pnpm
pnpm add @kitsunechaos/use-outside-click
Links
- 📦 npm → npmjs.com/package/@kitsunechaos/use-outside-click
- 🐙 GitHub → github.com/kitsunechaos-labs/use-outside-click
If this saved you from writing that boilerplate one more time — drop a ⭐ on GitHub. It genuinely helps other developers discover it.
Built with 🦊 by kitsunechaos