useClickOutside
UI/Interaction
A React hook that detects clicks outside of a specified element, perfect for modals, dropdowns, and popups.
Demo
Click the button to open the dropdown, then click outside to close it:
Installation
npm install @thibault.sh/hooks
Usage
import { useClickOutside } from '@thibault.sh/hooks/useClickOutside';
import { useRef, useState } from 'react';
function Modal() {
const [isOpen, setIsOpen] = useState(false);
const modalRef = useRef<HTMLDivElement>(null);
useClickOutside(modalRef, (event) => {
if (isOpen && modalRef.current && !modalRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
});
return (
<>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<div className="modal-overlay">
<div ref={modalRef} className="modal-content">
<h2>Modal Content</h2>
<p>Click outside to close</p>
</div>
</div>
)}
</>
);
}
API
Parameters
ref
RefObject<HTMLElement>React ref object for the element to monitor
handler
(event: MouseEvent | TouchEvent) => voidCallback function to execute when click outside occurs
Type Safety Notes:
- • The hook accepts refs to any HTML element type (div, button, etc.)
- • You should check if the ref is available before accessing it in the handler
- • The event target should be cast to Node when using contains()
Features
- ✓Touch Support
Works with both mouse clicks and touch events
- ✓Type Safety
Full TypeScript support for element refs
- ✓Cleanup
Automatically removes event listeners when component unmounts
- ✓Performance
Uses event delegation for efficient handling
Context Menu Example
import { useClickOutside } from '@thibault.sh/hooks';
import { useRef, useState } from 'react';
interface Position {
x: number;
y: number;
}
function ContextMenu() {
const [isOpen, setIsOpen] = useState(false);
const [position, setPosition] = useState<Position>({ x: 0, y: 0 });
const menuRef = useRef<HTMLDivElement>(null);
useClickOutside(menuRef, (event) => {
if (isOpen && menuRef.current && !menuRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
});
const handleContextMenu = (e: React.MouseEvent) => {
e.preventDefault();
setIsOpen(true);
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onContextMenu={handleContextMenu} className="min-h-screen">
<p>Right click anywhere to open the context menu</p>
{isOpen && (
<div
ref={menuRef}
style={{
position: 'fixed',
top: position.y,
left: position.x
}}
className="bg-white border shadow-lg rounded-md p-2"
>
<button className="block w-full p-2 hover:bg-gray-100">
Copy
</button>
<button className="block w-full p-2 hover:bg-gray-100">
Paste
</button>
<button className="block w-full p-2 hover:bg-gray-100">
Delete
</button>
</div>
)}
</div>
);
}
Tooltip Example
import { useClickOutside } from '@thibault.sh/hooks';
import { useRef, useState } from 'react';
function TooltipWithClickOutside() {
const [isOpen, setIsOpen] = useState(false);
const tooltipRef = useRef<HTMLDivElement>(null);
useClickOutside(tooltipRef, (event) => {
if (isOpen && tooltipRef.current && !tooltipRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
});
return (
<div className="relative inline-block">
<button
onClick={() => setIsOpen(!isOpen)}
className="info-button"
>
?
</button>
{isOpen && (
<div
ref={tooltipRef}
className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 p-4 bg-black text-white rounded shadow-lg"
>
<h3 className="font-bold mb-2">Help Information</h3>
<p>This tooltip stays open until you click outside.</p>
<p>Click anywhere outside to close it.</p>
</div>
)}
</div>
);
}