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.
The website redesign is ready for review.
"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.