@lattice-ui/layer Part of the stable direction toward v1.0.
What It Is For
Use layer when you need the shared mechanics behind portals, outside dismissal, and mount-presence transitions.
It is the coordination layer that powers overlays like dialogs, popovers, menus, selects, and tooltips.
TSX Example Preview
Preview the first lines below, or expand to inspect the full source file.
import { DismissableLayer, PortalProvider, Presence } from "@lattice-ui/layer";
import React from "@rbxts/react";
type Props = {
playerGui: PlayerGui;
};
... import { DismissableLayer, PortalProvider, Presence } from "@lattice-ui/layer";
import React from "@rbxts/react";
type Props = {
playerGui: PlayerGui;
};
function LayerDemo() {
const [open, setOpen] = React.useState(true);
return (
<frame BackgroundTransparency={1} Size={UDim2.fromOffset(420, 220)}>
<textbutton
AutoButtonColor={false}
BackgroundColor3={Color3.fromRGB(60, 76, 104)}
BorderSizePixel={0}
Size={UDim2.fromOffset(180, 36)}
Text={open ? "Hide layer" : "Show layer"}
TextColor3={Color3.fromRGB(236, 240, 248)}
TextSize={14}
Event={{ Activated: () => setOpen((value) => !value) }}
>
<uicorner CornerRadius={new UDim(0, 8)} />
</textbutton>
<Presence
present={open}
render={({ isPresent }) => (
<DismissableLayer
enabled={isPresent}
onDismiss={() => setOpen(false)}
>
<frame
AnchorPoint={new Vector2(0, 0)}
BackgroundColor3={Color3.fromRGB(34, 41, 54)}
BorderSizePixel={0}
Position={UDim2.fromOffset(0, 54)}
Size={UDim2.fromOffset(300, 120)}
>
<uicorner CornerRadius={new UDim(0, 10)} />
<textlabel
BackgroundTransparency={1}
Position={UDim2.fromOffset(12, 12)}
Size={UDim2.fromOffset(276, 52)}
Text="DismissableLayer owns outside interaction. Presence keeps the subtree mounted while it exits."
TextColor3={Color3.fromRGB(236, 240, 248)}
TextSize={14}
TextWrapped={true}
TextXAlignment={Enum.TextXAlignment.Left}
TextYAlignment={Enum.TextYAlignment.Top}
/>
</frame>
</DismissableLayer>
)}
/>
</frame>
);
}
export function LayerExample(props: Props) {
return (
<PortalProvider container={props.playerGui}>
<LayerDemo />
</PortalProvider>
);
} Install
Global CLI command: lattice add layer
Monorepo local script
Use your package manager wrapper when running the local lattice command.
pnpm lattice add layerPublic Exports
-
DismissableLayer -
Portal -
PortalProvider -
usePortalContext -
Presence
State Model
PortalProviderestablishes the overlay container and a display-order base for everything mounted beneath it.DismissableLayertracks whether outside interactions should dismiss the current surface and whether outside pointer events should be blocked.Presencekeeps a subtree mounted long enough for exit work to complete and reportsisPresentto the render function.
Key API
PortalProvider
Put this near the UI root with a PlayerGui container so overlay packages share one predictable mounting target.
Portal
Use Portal when content should render outside its logical parent tree but keep the same React ownership.
DismissableLayer
Use outside-interaction callbacks and onDismiss to implement overlay close behavior without manual global input listeners.
Presence
Use present, exitFallbackMs, and onExitComplete to coordinate presence-driven mounting and unmounting.
Composition Patterns
Overlay root setup
Mount one PortalProvider at app root and let all dialog, popover, menu, select, and tooltip content inherit it.
Custom layered surfaces
Compose Portal, DismissableLayer, and Presence directly when you need a custom overlay that does not map cleanly to an existing package.
Cautions / Limits
- Without a stable
PortalProvider, overlays can still work but become harder to reason about and coordinate across the app. DismissableLayerhandles outside interaction semantics; it does not provide visuals or positioning by itself.- Use
motionfor animated enter, exit, response, and feedback behavior instead of adding animation policy to layer consumers.
Decision Guides
- Overlay and state pitfalls
Review PortalProvider placement, stacking rules, and dismissable layer coordination.