Skip to main content

Overview

useClickOutside is a hook that triggers a callback when a click or touch event occurs outside of the specified element(s). This is commonly used for closing dropdowns, modals, and popovers when clicking outside of them.

Import

import { useClickOutside } from "@zayne-labs/toolkit-react";

Signature

const useClickOutside = <TElement extends HTMLElement>(
  options: UseClickOutsideOptions<TElement>
) => { ref: React.RefObject<TElement> }

Parameters

options
UseClickOutsideOptions<TElement>
required
Configuration options for the click outside behavior.
options.onClick
(event: MouseEvent | TouchEvent) => void
required
Callback function that will be invoked when a click/touch occurs outside the element(s).
options.ref
React.RefObject<TElement> | Array<React.RefObject<TElement>>
A ref or array of refs to elements. Clicks outside these elements will trigger the callback. If not provided, the hook returns an internal ref that you can use.
options.enabled
boolean
default:"true"
Whether the click outside detection is enabled.

Return Value

ref
React.RefObject<TElement>
An internal ref that can be attached to an element. Only needed if you don’t provide your own ref via options.

Usage

Basic Dropdown

import { useClickOutside } from "@zayne-labs/toolkit-react";
import { useState } from "react";

function Dropdown() {
  const [isOpen, setIsOpen] = useState(false);
  
  const { ref } = useClickOutside<HTMLDivElement>({
    onClick: () => setIsOpen(false),
    enabled: isOpen, // Only listen when dropdown is open
  });

  return (
    <div ref={ref}>
      <button onClick={() => setIsOpen(!isOpen)}>
        Toggle Menu
      </button>
      
      {isOpen && (
        <ul className="dropdown-menu">
          <li>Option 1</li>
          <li>Option 2</li>
          <li>Option 3</li>
        </ul>
      )}
    </div>
  );
}

Using Your Own Ref

import { useClickOutside } from "@zayne-labs/toolkit-react";
import { useRef, useState } from "react";

function Modal() {
  const [isOpen, setIsOpen] = useState(false);
  const modalRef = useRef<HTMLDivElement>(null);
  
  useClickOutside<HTMLDivElement>({
    ref: modalRef,
    onClick: () => setIsOpen(false),
  });

  if (!isOpen) return null;

  return (
    <div className="modal-overlay">
      <div ref={modalRef} className="modal-content">
        <h2>Modal Title</h2>
        <p>Click outside to close</p>
      </div>
    </div>
  );
}

Multiple Elements

import { useClickOutside } from "@zayne-labs/toolkit-react";
import { useRef, useState } from "react";

function ComplexMenu() {
  const [isOpen, setIsOpen] = useState(false);
  const buttonRef = useRef<HTMLButtonElement>(null);
  const menuRef = useRef<HTMLDivElement>(null);
  
  // Clicking outside both the button and menu will close it
  useClickOutside<HTMLElement>({
    ref: [buttonRef, menuRef],
    onClick: () => setIsOpen(false),
    enabled: isOpen,
  });

  return (
    <div>
      <button
        ref={buttonRef}
        onClick={() => setIsOpen(!isOpen)}
      >
        Open Menu
      </button>
      
      {isOpen && (
        <div ref={menuRef} className="menu">
          <p>Menu content</p>
        </div>
      )}
    </div>
  );
}

With Event Details

import { useClickOutside } from "@zayne-labs/toolkit-react";
import { useState } from "react";

function EventDetailsExample() {
  const [isOpen, setIsOpen] = useState(false);
  
  const { ref } = useClickOutside<HTMLDivElement>({
    onClick: (event) => {
      console.log("Clicked outside at:", event.clientX, event.clientY);
      console.log("Target element:", event.target);
      setIsOpen(false);
    },
  });

  return (
    <div ref={ref}>
      <button onClick={() => setIsOpen(!isOpen)}>
        Toggle Panel
      </button>
      
      {isOpen && (
        <div className="panel">
          <p>Panel content</p>
        </div>
      )}
    </div>
  );
}

Conditional Behavior

import { useClickOutside } from "@zayne-labs/toolkit-react";
import { useState } from "react";

function ConditionalExample() {
  const [isOpen, setIsOpen] = useState(false);
  const [isPinned, setIsPinned] = useState(false);
  
  const { ref } = useClickOutside<HTMLDivElement>({
    onClick: () => setIsOpen(false),
    // Only enable when open and not pinned
    enabled: isOpen && !isPinned,
  });

  return (
    <div ref={ref}>
      <button onClick={() => setIsOpen(!isOpen)}>
        Toggle Sidebar
      </button>
      
      {isOpen && (
        <div className="sidebar">
          <button onClick={() => setIsPinned(!isPinned)}>
            {isPinned ? "Unpin" : "Pin"}
          </button>
          <p>Sidebar content</p>
        </div>
      )}
    </div>
  );
}

Notes

  • The hook listens to both mousedown and touchstart events for broad device support
  • Event listeners are automatically cleaned up when the component unmounts or when enabled is false
  • The onClick callback is kept stable using useCallbackRef to prevent unnecessary listener updates
  • When providing an array of refs, a click is considered “outside” only if it’s outside ALL specified elements
  • Built on top of the onClickOutside utility from @zayne-labs/toolkit-core

Build docs developers (and LLMs) love