useDismiss#
useDismiss registers an open overlay with a shared dismissal stack. Pressing
Escape or pressing outside the overlay dismisses the topmost registered layer,
so nested overlays close one at a time, innermost first.
See also: useFocusReturn, usePosition, useSafeArea.
Demo#
import { useDismiss, type DismissReason } from 'hono-preact-ui';
import { useRef, useState } from 'preact/hooks';
// A panel registered with the dismissal stack. Pressing Escape or clicking
// outside the panel dismisses it; the readout shows which path fired.
// Styling: .docs-dismiss* in root.css.
export function UseDismissDemo() {
const ref = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(false);
const [reason, setReason] = useState<DismissReason | null>(null);
useDismiss({
enabled: open,
refs: [ref],
onDismiss: (r) => {
setReason(r);
setOpen(false);
},
});
return (
<div class="docs-dismiss">
<button
type="button"
class="docs-dismiss-trigger"
onClick={() => {
setReason(null);
setOpen(true);
}}
>
Open panel
</button>
{open ? (
<div
ref={ref}
class="docs-dismiss-panel"
role="dialog"
aria-label="Dismissable panel"
>
Press Escape or click outside to dismiss.
</div>
) : null}
{reason ? (
<p class="docs-dismiss-readout">
dismissed via: <strong>{reason}</strong>
</p>
) : null}
</div>
);
}
Example#
import { useDismiss } from 'hono-preact-ui';
import { useRef } from 'preact/hooks';
function Panel({ open, onClose }: { open: boolean; onClose: () => void }) {
const ref = useRef<HTMLDivElement>(null);
useDismiss({
enabled: open,
refs: [ref],
onDismiss: onClose,
});
return open ? <div ref={ref}>panel</div> : null;
}
Signature#
import { useDismiss } from 'hono-preact-ui';
function useDismiss(opts: UseDismissOptions): void;
interface UseDismissOptions {
enabled: boolean; // typically the open state
refs: RefObject<HTMLElement>[]; // elements treated as inside (no outside-press)
escape?: boolean; // default true
outsidePress?: boolean; // default true
id?: string; // dismiss-tree node id (nested menus); omit for a single-node layer
parentId?: string | null; // parent node id for submenu coordination
onDismiss: (reason: 'escape' | 'outside-press') => void;
}
Options#
| Option | Type | Default | Notes |
|---|---|---|---|
enabled | boolean | none | Register only while open. |
refs | RefObject[] | none | Elements treated as inside (no outside-press). |
escape | boolean | true | Dismiss on Escape. |
outsidePress | boolean | true | Dismiss on an outside pointer press. |
id | string | optional | Dismiss-tree node id for nested menus; omit for a single-node layer (Popover / Tooltip). |
parentId | string | null | optional | Parent node's id for submenu coordination; omit at the tree root. |
onDismiss | (reason) => void | none | Called with 'escape' or 'outside-press'. |
The listeners are document-level and capture-phase, attached once and shared across every registered layer, so adding overlays does not multiply listeners.