@lattice-ui/dialog Part of the stable direction toward v1.0.
What It Is For
Use Dialog for modal or non-modal overlays that own focus handoff, outside dismissal, and explicit close actions.
The package gives you the compound structure for triggers, content, overlay, and close controls while leaving rendering and layout fully headless.
import { Dialog } from "@lattice-ui/dialog";
import { PortalProvider } from "@lattice-ui/layer";
import React from "@rbxts/react";
type Props = {
playerGui: PlayerGui;
};
function DialogDemo() {
const [open, setOpen] = React.useState(false);
return (
<Dialog.Root onOpenChange={setOpen} open={open}>
<Dialog.Trigger asChild>
<textbutton
AutoButtonColor={false}
BackgroundColor3={Color3.fromRGB(53, 104, 196)}
BorderSizePixel={0}
Size={UDim2.fromOffset(180, 40)}
Text={open ? "Dialog open" : "Open dialog"}
TextColor3={Color3.fromRGB(240, 244, 250)}
TextSize={14}
>
<uicorner CornerRadius={new UDim(0, 8)} />
</textbutton>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content trapFocus={true} restoreFocus={true}>
<Dialog.Overlay />
<frame
AnchorPoint={new Vector2(0.5, 0.5)}
BackgroundColor3={Color3.fromRGB(34, 41, 54)}
BorderSizePixel={0}
Position={UDim2.fromScale(0.5, 0.5)}
Size={UDim2.fromOffset(360, 180)}
>
<uicorner CornerRadius={new UDim(0, 10)} />
<textlabel
BackgroundTransparency={1}
Position={UDim2.fromOffset(18, 18)}
Size={UDim2.fromOffset(320, 48)}
Text="Dialog content owns the interaction mode and restores focus to the trigger on close."
TextColor3={Color3.fromRGB(236, 240, 248)}
TextSize={16}
TextWrapped={true}
TextXAlignment={Enum.TextXAlignment.Left}
TextYAlignment={Enum.TextYAlignment.Top}
/>
<Dialog.Close asChild>
<textbutton
AutoButtonColor={false}
BackgroundColor3={Color3.fromRGB(53, 104, 196)}
BorderSizePixel={0}
Position={UDim2.fromOffset(18, 122)}
Size={UDim2.fromOffset(120, 36)}
Text="Close"
TextColor3={Color3.fromRGB(240, 244, 250)}
TextSize={14}
>
<uicorner CornerRadius={new UDim(0, 8)} />
</textbutton>
</Dialog.Close>
</frame>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
export function DialogExample(props: Props) {
return (
<PortalProvider container={props.playerGui}>
<DialogDemo />
</PortalProvider>
);
} Live demo is experimental and may contain bugs.
Install
Global CLI command: lattice add dialog
Monorepo local script
Use your package manager wrapper when running the local lattice command.
pnpm lattice add dialogPublic Exports
-
Dialog -
Dialog.Root -
Dialog.Trigger -
Dialog.Portal -
Dialog.Content -
Dialog.Overlay -
Dialog.Close -
DialogClose -
DialogContent -
DialogOverlay -
DialogPortal -
DialogRoot -
DialogTrigger
State Model
Dialog.Rootcan be controlled withopenor uncontrolled withdefaultOpen.modaldefaults the interaction model for the whole dialog tree, andDialog.Contentcontrols focus trapping and focus restoration.- Portal, overlay, and outside-interaction dismissal are coordinated through shared layer state, not through userland event wiring.
Key API
Dialog.Root
Use open, defaultOpen, onOpenChange, and modal to define how the dialog participates in app state and surrounding interaction.
Dialog.Content
Use trapFocus, restoreFocus, and outside-interaction callbacks when you need to customize the default overlay behavior.
Dialog.Overlay
Mount the backdrop separately so you can own the visual treatment without re-implementing overlay semantics.
Dialog.Close
Attach close behavior to any host element without coupling it to a specific button implementation.
Composition Patterns
Settings or confirmation modal
Use Trigger, Portal, Overlay, Content, and Close as separate pieces so layout and visual hierarchy remain fully app-owned.
Managed focus return
Keep dialogs tied to a concrete trigger when possible so restoreFocus returns the user to a predictable origin after dismissal.
Cautions / Limits
- Dialogs depend on portal-based layering for reliable overlay behavior; use
PortalProvidernear the app root. - A dialog is the right fit when content should own the interaction mode; use
Popoverfor lighter anchored surfaces. - Keep overlay fade and panel reveal in
motion; do not make dialog focus or dismissal logic depend on custom tweens.
Decision Guides
- Choosing between controls
Compare Dialog vs Popover when deciding whether a surface should own interaction mode.
- Overlay and state pitfalls
Review PortalProvider, overlay stacking, and focus restoration before shipping dialogs.