557

React useClickOutside: dismiss on outside click in one hook

Dropdowns, modals, and popovers all need the same outside-click logic — one hook covers it, and points at any target you already have.

August 18, 2026

useClickOutside runs a callback when the user clicks outside a target — the logic behind every dropdown, modal, and popover. Its whole point: a task you reach for constantly becomes one hook and one line. You get a ref to attach, or you point it at a target you already have.

One hook, one line

Call it with what should happen on an outside click. It hands you a ref — attach it, and you're done:

import { useClickOutside } from '@siberiacancode/reactuse';
import { useState } from 'react';
 
function Dropdown() {
  const [open, setOpen] = useState(true);
 
  const ref = useClickOutside<HTMLDivElement>(() => setOpen(false));
 
  if (!open) return null;
 
  return <div ref={ref}>Click outside me to close</div>;
}

Clicks inside the element are ignored; clicks anywhere else run your callback.

The same three lines, everywhere

Dropdowns, context menus, date pickers, comboboxes — they all share this shape: a trigger plus a panel, dismissed on outside click. Keep both inside the ref and it just works:

import { useClickOutside } from '@siberiacancode/reactuse';
import { useState } from 'react';
 
function Menu() {
  const [open, setOpen] = useState(false);
  const ref = useClickOutside<HTMLDivElement>(() => setOpen(false));
 
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button onClick={() => setOpen((prev) => !prev)}>Options</button>
 
      {open && (
        <ul style={{ position: 'absolute', top: '100%' }}>
          <li>Profile</li>
          <li>Settings</li>
          <li>Log out</li>
        </ul>
      )}
    </div>
  );
}

The trigger lives inside the ref, so clicking it to open never counts as "outside" — it opens, and a click anywhere else closes it.

Already have the element? Pass a target

Don't want the hook's ref? Pass your own target as the first argument, callback second:

import { useClickOutside } from '@siberiacancode/reactuse';
import { useRef } from 'react';
 
function Modal({ onClose }: { onClose: () => void }) {
  const ref = useRef<HTMLDivElement>(null);
 
  useClickOutside(ref, onClose);
 
  return <div ref={ref}>{/* modal content */}</div>;
}

Ref or selector — same hook

The target doesn't have to be a ref. Wrap a selector, a DOM element, or an element-returning function in target() and the hook resolves it — no ref created just to satisfy the hook:

import { target, useClickOutside } from '@siberiacancode/reactuse';
 
// a selector, no ref to attach
useClickOutside(target('#menu'), () => close());

Takeaways

  • One hook, one line — the outside-click task behind every dropdown and modal, handled in a single call.
  • The same shape everywhere — trigger plus panel inside one ref covers menus, pickers, and comboboxes alike.
  • Point it at anything — take the ref it returns, pass your own, or wrap a selector or element in target().