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>
);
}React Native design for iOS, Android, and Web.
Quick Links
Resources