Core positioning hook for rendering overlay content using CSS Anchor Positioning and the Popover API. Use it as the foundation for custom popovers, hover cards, tooltips, and fixed-position layers when higher-level components are not enough. Both modes use theme body text defaults and end ancestor surface/group membership as a whole, including group-owned state. Explicit props and unrelated contexts remain unchanged. Apply intentional formatting through render styles or content, and establish complete content-local providers inside the rendered layer.
Positioning strategy: context uses CSS anchor positioning relative to a trigger ref; fixed uses explicit x/y coordinates.
onShow
() => void
Callback fired when the layer becomes visible.
onHide
() => void
Callback fired when the layer is hidden.
lightDismiss
boolean · optional · default false
Whether clicking outside should dismiss the layer using native popover light-dismiss behavior.
lazyMount
boolean · optional · default false
Context mode only. Wait until show() to resolve the inline/portal position and mount content; hide unmounts the content while the inert marker remains.
Returns
Name
Type and behavior
ref
RefCallback<HTMLElement> | undefined
Trigger ref for context mode. Undefined in fixed mode.
anchorId
string
CSS anchor name for context mode positioning.
show
() => void
Imperatively show the layer.
hide
() => void
Imperatively hide the layer.
isOpen
boolean
Whether the layer is currently open.
id
string
Unique ID for aria-describedby or other ARIA relationships.
Render function for the popover element. Pass placement/alignment in context mode or x/y in fixed mode. Placement/alignment are logical: they map to the self-* position-area keyword family, which resolves against the popover's own inherited direction, so RTL contexts mirror automatically in pure CSS. Pass positioning: "custom" in context mode to author position styles yourself via style (e.g. explicit anchor() insets or an anchor-size() cover): the hook keeps the popover behavior and position-anchor wiring but derives no position styles, including the automatic RTL mirroring, which becomes your responsibility. Pass offset (a CSS length; a number is px) in context mode for clearance from the anchor: it applies to both edges of the placement axis, so the gap survives a flip. Layers are flush by default. Context mode first renders an inert <template> marker in matching server and client markup. The final layer stays at that JSX position if its parent is safe; otherwise it is portaled to the nearest ancestor outside paragraphs, links, buttons, inline formatting, and structurally restricted containers. The nearest safe host keeps CSS custom properties inheriting live, while the layer preserves direction and writing mode from its JSX position. By default this resolution occurs after hydration so closed-layer DOM remains available; lazyMount defers it until show() and unmounts the content again on hide while the marker remains. The Popover API promotes the layer to the top layer when shown, so it escapes ancestor clipping and stacking wherever it is hosted. When the layer would overflow the viewport, position-try fallbacks flip it to the opposite side; centered layers additionally slide along the alignment axis (span fallbacks) so they stay on-screen near viewport edges.
Showcases and examples
1 documented example
useLayer — Anchored Layer
Low-level anchored overlay rendered with useLayer and a custom surface.
Preview loads on approachPreview loads on approach
Exact source · layer-hook-usage
// Copyright (c) Meta Platforms, Inc. and affiliates.
'use client';
import {useLayer} from '@astryxdesign/core/Layer';
import {Button} from '@astryxdesign/core/Button';
import {Card} from '@astryxdesign/core/Card';
import {Center} from '@astryxdesign/core/Center';
import {VStack} from '@astryxdesign/core/Layout';
import {Text} from '@astryxdesign/core/Text';
export default function LayerHookUsage() {
const layer = useLayer({mode: 'context', lightDismiss: true});
return (
<Center height={220}>
<Button
label={layer.isOpen ? 'Hide layer' : 'Show layer'}
ref={layer.ref}
onClick={layer.isOpen ? layer.hide : layer.show}
/>
{layer.render(
<Card padding={3}>
<VStack gap={1}>
<Text type="body" weight="bold">
Anchored content
</Text>
<Text type="body" color="secondary">
useLayer provides positioning; you own semantics and surface.
</Text>
</VStack>
</Card>,
{placement: 'below', alignment: 'center'},
)}
</Center>
);
}
Usage
Core positioning hook for rendering overlay content using CSS Anchor Positioning and the Popover API. Use it as the foundation for custom popovers, hover cards, tooltips, and fixed-position layers when higher-level components are not enough. Both modes use theme body text defaults and end ancestor surface/group membership as a whole, including group-owned state. Explicit props and unrelated contexts remain unchanged. Apply intentional formatting through render styles or content, and establish complete content-local providers inside the rendered layer.
Use context mode for anchor-positioned overlays relative to a trigger element, and fixed mode for manually positioned overlays at specific coordinates.
Build on higher-level components like Popover, HoverCard, and Tooltip for common overlay patterns.
Rely on the Popover API top layer to escape ancestor clipping and stacking, and host the layer near its trigger rather than in the body so it inherits the trigger's theme cascade and keeps a natural focus order.
Implement ARIA patterns directly in a Layer unless you also own the full accessibility behavior.