Latticereference

Layer

Portals into ScreenGuis, dismissable outside-press layers, and presence mounting for overlays and anchored surfaces in Roblox.

@lattice-ui/react-layerStable directiondepends on runtime, focus

Layer provides the three behaviours every overlay surface needs. It renders outside the local tree into a ScreenGui, dismisses on outside interaction while optionally blocking input behind the surface, and stays mounted through an exit animation. Dialog, Popover, Menu, Select, Tooltip and Toast are all built on it.

DismissableLayer renders through Portal and wraps its children in a FocusLayerProvider, so stacked layers trap and order correctly.

Import

import {
Portal,
PortalProvider,
usePortalContext,
DismissableLayer,
Presence,
} from "@lattice-ui/react-layer";

API reference

PortalProvider

function PortalProvider(props: {
container: BasePlayerGui;
displayOrderBase?: number;
children?: React.ReactNode;
}): React.Element;
Setting the portal target near the app root
<PortalProvider container={playerGui}>
<App />
</PortalProvider>

Establishes the portal context: the BasePlayerGui that portalled surfaces render into, and the base DisplayOrder used to stack layers (displayOrderBase, default 1000). Mount it once near your app root. It is a strict context provider — readers throw if no provider is above them.

PropTypeDescription
container *BasePlayerGuiThe PlayerGui (or comparable BasePlayerGui) that portalled surfaces render into.
displayOrderBasenumberBase DisplayOrder for generated ScreenGuis. Layers stack above it by mount order. Defaults to 1000.
childrenReact.ReactNodeThe subtree that can portal into this container.

usePortalContext

function usePortalContext(): {
container: BasePlayerGui;
displayOrderBase: number;
};

Reads the resolved container and displayOrderBase, throwing outside a PortalProvider. Portal and DismissableLayer both use it.

Portal

function Portal(props: {
children?: React.ReactNode;
container?: Instance;
}): React.Element;
Rendering content into the PlayerGui
<Portal>
<screengui>
<frame Size={UDim2.fromScale(1, 1)} />
</screengui>
</Portal>

Renders its children elsewhere in the Roblox instance tree through ReactRoblox.createPortal, while keeping them in the React tree so context and state still flow. It targets the nearest PortalProvider’s container unless you pass one. Render your own screengui inside if you need a LayerCollector — Portal only relocates the subtree.

PropTypeDescription
childrenReact.ReactNodeContent to render at the target instead of in place.
containerInstanceOverride the render target. Defaults to the PortalProvider container.

DismissableLayer

function DismissableLayer(props: {
children?: React.ReactNode;
enabled?: boolean;
contentBoundaryRef?: React.MutableRefObject<GuiObject | undefined>;
insideRefs?: Array<React.MutableRefObject<GuiObject | undefined>>;
modal?: boolean;
disableOutsidePointerEvents?: boolean;
onPointerDownOutside?: (event: LayerInteractEvent) => void;
onInteractOutside?: (event: LayerInteractEvent) => void;
onDismiss?: () => void;
}): React.Element;
A dismissable surface
const contentRef = React.useRef<Frame>();
<DismissableLayer
modal
contentBoundaryRef={contentRef}
onDismiss={() => setOpen(false)}
>
<frame ref={contentRef} Size={UDim2.fromOffset(280, 160)} />
</DismissableLayer>;

The core overlay primitive. It portals a ScreenGui whose DisplayOrder is the provider base plus this layer’s mount order. It registers on the shared layer stack and watches for pointer interactions outside its content boundary. A press outside fires onPointerDownOutside, any other outside interaction fires onInteractOutside, and unless vetoed with preventDefault, onDismiss follows.

The boundary defaults to the layer’s internal content wrapper. contentBoundaryRef names a specific GuiObject as “inside”, and insideRefs marks additional instances such as a separate trigger. With modal or disableOutsidePointerEvents, the layer renders a full-screen blocker behind the content.

PropTypeDescription
childrenReact.ReactNodeThe layer contents, rendered inside the portalled ScreenGui.
enabledbooleanWhether the layer participates in dismissal and input blocking. Defaults to true.
contentBoundaryRefReact.MutableRefObject<GuiObject | undefined>GuiObject treated as the inside boundary. Defaults to the internal content wrapper.
insideRefsArray<React.MutableRefObject<GuiObject | undefined>>Extra instances treated as inside, so interacting with them does not dismiss.
modalbooleanBlocks input behind the surface and enables outside-press dismissal.
disableOutsidePointerEventsbooleanBlocks pointer input behind the surface without implying full modality.
onPointerDownOutside(event: LayerInteractEvent) => voidCalled on a pointer press outside the boundary, before dismissal.
onInteractOutside(event: LayerInteractEvent) => voidCalled on any other outside interaction, before dismissal.
onDismiss() => voidCalled to request dismissal when an outside interaction is not vetoed.

LayerInteractEvent

type LayerInteractEvent = {
originalEvent: InputObject;
defaultPrevented: boolean;
preventDefault: () => void;
};

The event handed to onPointerDownOutside and onInteractOutside. originalEvent is the Roblox InputObject. preventDefault() keeps the layer from dismissing, and defaultPrevented reflects whether it has been vetoed.

Presence

function Presence(props: {
present: boolean;
exitFallbackMs?: number;
onExitComplete?: () => void;
children?: PresenceRender;
render?: PresenceRender;
}): React.Element | undefined;
type PresenceRender = (state: {
isPresent: boolean;
onExitComplete: () => void;
}) => React.ReactElement | undefined;
Keeping content mounted through its exit
<Presence present={open}>
{({ isPresent, onExitComplete }) => (
<AnimatedSurface visible={isPresent} onHidden={onExitComplete} />
)}
</Presence>

A mount controller for exit animations: Presence keeps its content mounted after present flips to false, then unmounts once the animation signals completion. It calls its render function with isPresent, which you drive your animation off, and an onExitComplete to call when the exit finishes. If that is never called, the content is force-unmounted after exitFallbackMs (default 140).

PropTypeDescription
present *booleanWhether the content should be present. Flipping to false starts the exit.
exitFallbackMsnumberFallback timeout that force-unmounts if exit is never reported. Defaults to 140.
onExitComplete() => voidCalled once the exit completes, via report or fallback.
childrenPresenceRenderRender function receiving { isPresent, onExitComplete }.
renderPresenceRenderAlternative to children; same signature. Used when children is not provided.