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:

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


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