useClickOutside
Close a popover, menu, or inline editor when the pointer lands outside its root.
Installation
npx shadcn@latest add miskibin/chat-components/use-click-outsideUsage
"use client"
import { useRef, useState } from "react"
import { useClickOutside } from "@/hooks/use-click-outside"
export function Palette() {
const [open, setOpen] = useState(false)
const ref = useRef<HTMLDivElement>(null)
useClickOutside(ref, () => setOpen(false), open)
return (
<div ref={ref}>
<button onClick={() => setOpen((value) => !value)}>Search</button>
{open ? <div role="dialog">…</div> : null}
</div>
)
}Examples
Keep the handler stable
The effect re-subscribes whenever handler changes identity. An inline arrow is fine for a small component; in a hot path wrap it in useCallback.
const close = React.useCallback(() => setOpen(false), [])
useClickOutside(ref, close, open)Pair it with Escape
Pointer dismissal alone is not enough — give keyboard users the same exit.
useClickOutside(ref, close, open)
React.useEffect(() => {
if (!open) return
const onKey = (event: KeyboardEvent) => {
if (event.key === "Escape") close()
}
window.addEventListener("keydown", onKey)
return () => window.removeEventListener("keydown", onKey)
}, [open, close])Notes
API reference
useClickOutside(ref, handler, enabled?)
ref*RefObject<T | null>- The root to watch. Anything inside it counts as inside.
handler*() => void- Called on a
mousedownoutside the root — before focus moves, so a click straight onto another control still works. enabledboolean = true- Pass your open state so the listener only exists while it matters.