Skip to main content

useInteractOutside

useInteractOutside is a hook that detects clicks outside chosen elements, such as closing an expanded panel or canceling an editor.

Import

import { useInteractOutside } from '@vortexlabs/vortex';

Other links

Choose an inside or outside control.
"use client";

import { Button, Stack, Text, useInteractOutside } from "@vortexlabs/vortex";
import React from "react";

export const OutsideBoundaryExample = () => {
  const ref = React.useRef<HTMLDivElement>(null);
  const [message, setMessage] = React.useState("Choose an inside or outside control.");
  useInteractOutside([ref], () => setMessage("Outside interaction detected."));
  return (
    <Stack gap={4}>
      <Stack padding={4} backgroundColor="surface-subtlest" attributes={{ ref }}>
        <Button variant="secondary" onClick={() => setMessage("Inside interaction.")}>
          Inside the boundary
        </Button>
      </Stack>
      <Button variant="secondary">Outside the boundary</Button>
      <Text size="body-sm-desktop">{message}</Text>
    </Stack>
  );
};

Features

  • Treats multiple elements as one boundary.
  • Detects outside mouse, touch, and keyboard clicks.
  • Can pause detection while content is closed.

Pass boundary refs, a callback, and optional { disabled }. The callback receives the native click event; the hook returns nothing.

Multiple Elements

Include the trigger and content in the same boundary. Set disabled: !open to ignore outside clicks while closed.

"use client";

import { Button, Stack, Text, useInteractOutside } from "@vortexlabs/vortex";
import React from "react";

export const OutsideDisclosureExample = () => {
  const triggerRef = React.useRef<HTMLButtonElement>(null);
  const panelRef = React.useRef<HTMLDivElement>(null);
  const panelId = React.useId();
  const [open, setOpen] = React.useState(false);
  useInteractOutside([triggerRef, panelRef], () => setOpen(false), { disabled: !open });
  return (
    <Stack gap={4}>
      <Button
        ref={triggerRef}
        variant="secondary"
        attributes={{ "aria-expanded": open, "aria-controls": panelId }}
        onClick={() => setOpen((value) => !value)}
      >
        Project details
      </Button>
      <div ref={panelRef} id={panelId} hidden={!open}>
        <Stack padding={4} gap={3} backgroundColor="surface-subtlest">
          <Text>The website redesign is ready for review.</Text>
          <Button variant="secondary" onClick={() => setOpen(false)}>
            Close details
          </Button>
        </Stack>
      </div>
      <Button variant="tertiary">Outside the disclosure</Button>
    </Stack>
  );
};

A click that starts inside stays an inside interaction, even if its handler removes the element. Moving focus alone does not call the callback.

Use Drawer for a complete overlay. This hook does not handle Escape, trap focus, or restore focus.

Accessibility

Key
Description
EnterSpace
Activate an outside control to trigger its click and the outside callback.
Provide a close control; outside clicks must not be the only way to dismiss content.