Skip to main content

useClickOutside

BetaSince 0.1.0

Calls a function when the reader clicks outside an element, for closing a menu or a panel.

import { useClickOutside } from "@anantya/uix";

Overview

useClickOutside calls a function when the reader clicks or taps outside the elements you give it. It is for closing popups, as Popover and Dropdown Menu will. In product code, use it for state that ends on a click elsewhere, such as a selection.

Building block

For a popup, use Popover or Dropdown Menu, coming in v1, which close on a click outside already.

Missing a component? Ask for it instead of building your own.

Signature

function useClickOutside(
refs: RefObject<Element | null> | readonly RefObject<Element | null>[],
handler: (event: PointerEvent) => void,
options?: UseClickOutsideOptions,
): void;

interface UseClickOutsideOptions {
enabled?: boolean;
}

Parameters

ParameterTypeDefaultDescription
refsRefObject<Element | null> or an array of them-The elements that count as inside, such as the popup and the button that opens it.
handler(event: PointerEvent) => void-Called with the pointerup event of a click outside.
options.enabledbooleantrueListens while true, such as while the popup is open.

Returns

Nothing. It listens while enabled is true and stops when it turns false or the component unmounts.

Example

Select some invoices, then click anywhere outside them to clear the selection.

0 selected

Code
"use client";

import { Button, useClickOutside } from "@anantya/uix";
import { useRef, useState } from "react";

const invoices = ["INV-2026-0140", "INV-2026-0141", "INV-2026-0142"];

export default function ClearSelection() {
const [selected, setSelected] = useState<string[]>([]);
const list = useRef<HTMLDivElement>(null);
useClickOutside(list, () => setSelected([]), { enabled: selected.length > 0 });
const toggle = (id: string) =>
setSelected((current) =>
current.includes(id) ? current.filter((other) => other !== id) : [...current, id],
);

return (
<div className="flex flex-col items-center gap-3 text-sm">
<div ref={list} className="flex flex-wrap justify-center gap-2">
{invoices.map((id) => (
<Button
key={id}
size="sm"
variant={selected.includes(id) ? "primary" : "secondary"}
aria-pressed={selected.includes(id)}
onClick={() => toggle(id)}
>
{id}
</Button>
))}
</div>
<p className="text-muted-foreground">{selected.length} selected</p>
</div>
);
}

Notes

  • Pass the ref of every element that counts as inside, such as a button that starts the state too. Otherwise a click on it ends the state and starts it again
  • Pair it with useEscapeKey, so keyboard users can end the state too
  • A click counts only when it both starts and ends outside, so selecting text that ends outside closes nothing, and neither does a touch that scrolls the page
  • When several are enabled, only the one enabled last listens: a click outside a nested popup closes only that popup
  • It listens before other handlers, so stopPropagation() elsewhere can't block it
  • It does nothing on the server, or while no ref has an element

Changelog

Every change to useClickOutside since 0.1.0 is in the release notes.

© Anantya TechLabs. All rights reserved.