useClickOutside
A React hook that detects clicks outside of a specified element and triggers a callback. Perfect for closing modals, dropdowns, and popovers when interacting with other parts of the UI.
Installation
npx shadcn@latest add https://r.fiberui.com/r/hooks/use-click-outside.jsonFeatures
- Event Detection - Detects clicks outside of a specified element.
- Customizable Events - Supports
mousedown(default) ormouseupevents. - Touch Support - Automatically handles touch events for mobile devices.
Source Code
View the full hook implementation in the Hook Source Code section below.
Basic Usage
The useClickOutside hook takes a ref to the element you want to monitor and a callback function to run when a click occurs outside that element.
"use client";
import { useClickOutside } from "@repo/hooks/dom/use-click-outside";
import { useRef, useState } from "react";
export function Example1() {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useClickOutside(ref, () => {
setIsOpen(false);
});
return (
<div className="flex flex-col items-center gap-4 p-8">
<button
onClick={() => setIsOpen(true)}
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-4 py-2 text-sm font-medium transition-colors"
>
{isOpen ? "Modal Open" : "Open Modal"}
</button>
{isOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/20 backdrop-blur-sm">
<div
ref={ref}
className="bg-card text-card-foreground w-full max-w-sm rounded-lg border p-6 shadow-lg"
>
<h3 className="text-lg font-semibold">Modal Title</h3>
<p className="text-muted-foreground mt-2 text-sm">
Click outside this box to close it.
</p>
</div>
</div>
)}
</div>
);
}
Use Cases
Closing Modals/Dropdowns
The most common use case is closing a modal, dropdown, or popover when the user clicks somewhere else on the page.
Ignoring Specific Elements
You can ignore clicks on specific elements by checking the event target in your handler, or by ensuring the element you want to ignore is outside the ref (though typically you care about what's inside the ref).
(triggers count)
Outside Clicks: 0
"use client";
import { useClickOutside } from "@repo/hooks/dom/use-click-outside";
import { useRef, useState } from "react";
export function Example2() {
const [count, setCount] = useState(0);
const boxRef = useRef<HTMLDivElement>(null);
const ignoreRef = useRef<HTMLButtonElement>(null);
useClickOutside(boxRef, (event) => {
// Example of ignoring a specific element manually if needed,
// though typically you just put it inside the ref.
// But here, the ignore button is outside the box.
if (ignoreRef.current?.contains(event.target as Node)) {
return;
}
setCount((c) => c + 1);
});
return (
<div className="flex flex-col items-center gap-8 p-8">
<div
ref={boxRef}
className="bg-secondary text-secondary-foreground flex h-32 w-32 items-center justify-center rounded-lg border shadow-sm"
>
<span className="text-center text-sm font-medium">
Click outside me
<br />
(triggers count)
</span>
</div>
<div className="flex flex-col items-center gap-2">
<p className="font-mono text-sm">Outside Clicks: {count}</p>
<button
ref={ignoreRef}
className="bg-muted text-muted-foreground hover:bg-muted/80 rounded px-3 py-1 text-xs"
>
I am ignored (won't trigger count)
</button>
</div>
</div>
);
}
API Reference
function useClickOutside<T extends HTMLElement>(
ref: React.RefObject<T>,
handler: (event: MouseEvent | TouchEvent) => void,
mouseEvent?: "mousedown" | "mouseup",
): void;Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
ref | React.RefObject<T> | - | A React ref object pointing to the element to monitor. |
handler | (event: MouseEvent | TouchEvent) => void | - | The callback function to execute when a click outside occurs. |
mouseEvent | "mousedown" | "mouseup" | "mousedown" | The mouse event to listen for. |
Hook Source Code
import { RefObject, useEffect, useRef } from "react";
type Handler = (event: MouseEvent | TouchEvent) => void;
/**
* A hook that detects clicks outside of the specified element and calls the provided handler.
*
* @param ref - The ref of the element to detect clicks outside of.
* @param handler - The function to call when a click outside occurs.
* @param mouseEvent - The mouse event to listen for. Defaults to "mousedown".
*/
export function useClickOutside<T extends HTMLElement = HTMLElement>(
ref: RefObject<T | null>,
handler: Handler,
mouseEvent: "mousedown" | "mouseup" = "mousedown",
): void {
const savedHandler = useRef<Handler>(handler);
// Update the saved handler if it changes, to avoid re-running the effect
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const listener = (event: MouseEvent | TouchEvent) => {
const el = ref?.current;
// Do nothing if clicking ref's element or descendent elements
if (!el || el.contains(event.target as Node)) {
return;
}
savedHandler.current(event);
};
document.addEventListener(mouseEvent, listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener(mouseEvent, listener);
document.removeEventListener("touchstart", listener);
};
}, [ref, mouseEvent]);
}