logo

Nurav UI

Community
Hooks

useOutsideClick

A custom React hook that detects clicks outside of a specified element — perfect for closing dropdowns, modals, and popovers.Copy Markdown

Overview

useOutsideClick listens for mousedown and touchstart events on the document and fires a handler whenever the user interacts outside the referenced DOM node. It automatically cleans up its event listeners on unmount, preventing memory leaks.

Commonly used for:

  • Closing dropdown menus
  • Dismissing modals and drawers
  • Collapsing search results
  • Hiding tooltip / popover panels

Installation

Automatic (CLI)

The fastest way — pull the hook directly into your project.

Before using the @nurav-ui alias, add this to your components.json:

components.json
{
  "registries": {
    "@nurav-ui": "https://nurav-ui.vercel.app/r"
  }
}

Or skip alias setup entirely and use the direct URL below.

Terminal
npx shadcn@latest add @nurav-ui/use-outside-click

Alternatively, install via direct URL (no components.json changes needed):

Terminal
npx shadcn@latest add https://nurav-ui.vercel.app/r/use-outside-click.json

Manual Setup

Create the hook file

Copy the source into hooks/use-outside-click.ts:

hooks/use-outside-click.ts
import { useEffect, RefObject } from "react";

export function useOutsideClick(
  ref: RefObject<HTMLElement | null>,
  handler: (event?: MouseEvent | TouchEvent) => void,
) {
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent | TouchEvent) => {
      if (!ref.current || ref.current.contains(event.target as Node)) {
        return;
      }
      handler(event);
    };

    document.addEventListener("mousedown", handleClickOutside);
    document.addEventListener("touchstart", handleClickOutside);

    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
      document.removeEventListener("touchstart", handleClickOutside);
    };
  }, [ref, handler]);
}

Import and use

Import the hook anywhere in your component tree:

components/Dropdown.tsx
import { useRef } from "react";
import { useOutsideClick } from "@/hooks/use-outside-click";

Usage

Basic Example

import { useRef } from 'react';
import { useOutsideClick } from '@/hooks/use-outside-click';

export default function MyComponent() {
  const containerRef = useRef<HTMLDivElement>(null);

  useOutsideClick(containerRef, () => {
    console.log('Clicked outside!');
  });

  return (
    <div ref={containerRef} className="p-4 border rounded-lg">
      Click inside me — nothing happens.
      Click outside me — check the console.
    </div>
  );
}

A real-world pattern: a dropdown that closes when the user clicks outside it.

import { useRef, useState, useCallback } from 'react';
import { useOutsideClick } from '@/hooks/use-outside-click';

export default function Dropdown() {
  const [open, setOpen] = useState(false);
  const dropdownRef = useRef<HTMLDivElement>(null);

  const handleClose = useCallback(() => setOpen(false), []);
  useOutsideClick(dropdownRef, handleClose);

  return (
    <div ref={dropdownRef} className="relative inline-block">
      <button
        onClick={() => setOpen((prev) => !prev)}
        className="px-4 py-2 rounded-lg border"
      >
        Toggle Menu
      </button>

      {open && (
        <ul className="absolute mt-2 w-48 rounded-lg border bg-background shadow-lg">
          <li className="px-4 py-2 hover:bg-foreground/5 cursor-pointer">Profile</li>
          <li className="px-4 py-2 hover:bg-foreground/5 cursor-pointer">Settings</li>
          <li className="px-4 py-2 hover:bg-foreground/5 cursor-pointer">Sign out</li>
        </ul>
      )}
    </div>
  );
}

API Reference

Parameters

PropTypeDefaultDescription
refrequired
RefObject<HTMLElement | null>A React ref attached to the element you want to track.
handlerrequired
(event?: MouseEvent | TouchEvent) => voidCallback fired when a click outside the ref element is detected.

Return Value

This hook returns void — it operates purely via side effects.


Stable handler tip: Wrap your handler in useCallback to prevent the effect from re-running on every render when the handler is defined inline.

const handleClose = useCallback(() => setOpen(false), []);
useOutsideClick(ref, handleClose);

The hook listens on mousedown (not click) to ensure the handler fires before any onClick on inner elements — preventing race conditions with controlled state updates.

Built by Varun

Last Updated:April 10, 2026

On this page