useLayer

Register a custom overlay in the app-wide layer stack (no provider needed), so Escape and Android back close only the topmost layer, a press inside a nested layer doesn't close its parent, and focus moves in on open, can be trapped, and returns on close. Wrap its content in <LayerScope id={id}> so layers opened from inside it stack above it, and use useIsTopLayer(id) to know whether it is currently on top.
useLayer({ active, onDismiss, containerRef, … }) registers the panel while active is true and returns its { id }. onDismiss(reason) receives 'escape-key', 'back-button' or 'outside-press', and the panel stays open until you set active back to false; closeOnOutsidePress is off by default, and outsidePressIgnoreRefs stops a press on the trigger from counting as outside. Escape and outside press are web-only: on native, Android back dismisses the topmost layer, and a tap outside needs your own backdrop. modal: true walls off the layers below it and traps Tab, and focus moves into the panel on open and back on close (autoFocus, restoreFocus, both on by default).

Loading demo…

import { useRef, useState } from 'react'; import type { View } from 'react-native'; import { Badge, Block, Button, LayerScope, Text, useLayer } from '@plocks/ui'; import type { LayerDismissReason } from '@plocks/ui'; export function Demo() { const [opened, setOpened] = useState(false); const [reason, setReason] = useState<LayerDismissReason | null>(null); const triggerRef = useRef<View>(null); const panelRef = useRef<View>(null); const { id } = useLayer({ active: opened, onDismiss: (why) => { setReason(why); setOpened(false); }, closeOnOutsidePress: true, containerRef: panelRef, outsidePressIgnoreRefs: [triggerRef], }); const toggle = () => { setReason(null); setOpened((current) => !current); }; return ( <Block align="flex-start"> <Button ref={triggerRef} onPress={toggle}> {opened ? 'Close panel' : 'Open panel'} </Button> {opened ? ( <LayerScope id={id}> <Block ref={panelRef} p="md" radius="md" bg="elevated" shadow="md" miw={220}> <Text fw="600">Panel</Text> </Block> </LayerScope> ) : null} {reason ? <Badge variant="light">{reason}</Badge> : null} </Block> ); }