Skip to main content

useCanDismiss

useCanDismiss is a hook that checks whether an overlay can close, such as dismissing a nested menu before its parent.

Import

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

Other links

Open the parent boundary to begin.
"use client";

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

export const UseCanDismissOrderingExample = () => {
  const [parentOpen, setParentOpen] = React.useState(false);
  const [childOpen, setChildOpen] = React.useState(false);
  const [message, setMessage] = React.useState("Open the parent boundary to begin.");
  const parentRef = React.useRef<HTMLDivElement>(null);
  const childRef = React.useRef<HTMLDivElement>(null);
  const canDismissParent = useCanDismiss({
    active: parentOpen,
    contentRef: parentRef,
    hasTrigger: false,
  });
  const canDismissChild = useCanDismiss({
    active: childOpen,
    contentRef: childRef,
    hasTrigger: false,
  });
  const dismissParent = () => {
    if (canDismissParent()) {
      setParentOpen(false);
      setMessage("Parent dismissed.");
    } else setMessage("Dismiss the child boundary first.");
  };
  return (
    <Stack gap={3} width="100%">
      <Button
        variant="secondary"
        onClick={() => {
          setParentOpen(true);
          setMessage("Parent boundary opened.");
        }}
      >
        Open parent boundary
      </Button>
      {parentOpen && (
        <Stack
          gap={3}
          padding={4}
          backgroundColor="surface-subtlest"
          borderRadius="md"
          attributes={{ ref: parentRef }}
        >
          <Text weight="medium">Parent boundary</Text>
          <Stack direction="row" gap={3} wrap>
            <Button size="sm" onClick={() => setChildOpen(true)}>
              Open child boundary
            </Button>
            <Button size="sm" variant="secondary" onClick={dismissParent}>
              Request parent dismissal
            </Button>
          </Stack>
          {childOpen && (
            <Stack
              gap={3}
              padding={4}
              borderColor="default"
              borderRadius="md"
              attributes={{ ref: childRef }}
            >
              <Text>Child boundary</Text>
              <Button
                size="sm"
                variant="secondary"
                onClick={() => {
                  if (canDismissChild()) {
                    setChildOpen(false);
                    setMessage("Child dismissed. The parent can now close.");
                  }
                }}
              >
                Dismiss child boundary
              </Button>
            </Stack>
          )}
        </Stack>
      )}
      <Text attributes={{ role: "status" }}>{message}</Text>
    </Stack>
  );
};

Features

  • Registers an active dismissal boundary.
  • Checks whether a newer overlay should handle dismissal first.
  • Supports trigger-based nesting or explicit ordering without a trigger.

Call canDismiss() inside your dismissal handler. It checks ordering; your code closes the overlay. Pass active so closed overlays stop participating.

Trigger Nesting

Pass triggerRef to detect overlays opened inside another overlay. Use hasTrigger: false for ordering without trigger nesting.

const canDismiss = useCanDismiss({
  active: open,
  contentRef,
  triggerRef,
});

Nonmodal Overlays

Set modal: false so this overlay does not block another overlay’s dismissal. A newer modal overlay can still block it.

const canDismiss = useCanDismiss({
  active: open,
  contentRef,
  hasTrigger: false,
  modal: false,
});

Inactive instances return true; guard dismissal handlers with your open state. Registrations are removed on cleanup.

Use Drawer for a complete overlay. This hook does not render UI, trap focus, or lock scrolling.