useOutsideClick

Detects clicks outside of a specified DOM element, useful for closing modals or dropdowns.

Code

1import { useEffect, RefObject } from "react"
2
3export function useOutsideClick<T extends HTMLElement = HTMLElement>(
4  ref: RefObject<T>,
5  handler: (event: MouseEvent | TouchEvent) => void,
6): void {
7  useEffect(() => {
8    const listener = (event: MouseEvent | TouchEvent) => {
9      if (!ref.current || ref.current.contains(event.target as Node)) {
10        return
11      }
12      handler(event)
13    }
14
15    document.addEventListener("mousedown", listener)
16    document.addEventListener("touchstart", listener)
17
18    return () => {
19      document.removeEventListener("mousedown", listener)
20      document.removeEventListener("touchstart", listener)
21    }
22  }, [ref, handler])
23}

Usage Example

import { useRef, useState } from "react" import { useOutsideClick } from "@/hooks/use-outside-click" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" export function OutsideClickExample() { const [isOpen, setIsOpen] = useState(false) const menuRef = useRef(null) useOutsideClick(menuRef, () => { if (isOpen) setIsOpen(false) }) return ( <div className="relative"> <Button onClick={() => setIsOpen(!isOpen)}> {isOpen ? "Close Menu" : "Open Menu"} </Button> {isOpen && ( <Card ref={menuRef} className="absolute mt-2 p-4 w-48 bg-npui-card border-npui-border shadow-lg z-10"> <CardContent className="p-0"> <ul className="space-y-2"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </CardContent> </Card> )} <p className="mt-2 text-sm text-npui-muted-foreground">Click outside the menu to close it.</p> </div> ) }