Lattice components

Menu

Anchored action-menu primitive that owns open state, ordered selection movement, popper positioning, and layered dismissal while you own the visuals.

@lattice-ui/menu Stable direction import Menu depends on core , focus , layer , motion , popper

Menu is the primitive for a list of actions that opens from a trigger: context menus, dropdown actions, overflow menus, and command lists. It coordinates open state, ordered item movement, positioning, dismissal, and exit motion so your component only has to render the items and their contents.

Reach for Menu when you need a surface of selectable actions that moves selection in order (gamepad up/down and arrow keys), anchors to its trigger through the popper foundation, and dismisses on selection or outside interaction. Menu is modal by default — selection is trapped inside the open menu and restored to the trigger on close.

Import

import { Menu } from "@lattice-ui/menu";

Anatomy

Compose the parts you need. Root, Trigger, Portal, and Content form the working menu; Item makes it useful, and Group, Label, and Separator structure longer lists.

Menu anatomy
<Menu.Root>
<Menu.Trigger />
<Menu.Portal>
<Menu.Content>
<Menu.Label />
<Menu.Group>
<Menu.Item />
</Menu.Group>
<Menu.Separator />
<Menu.Item />
</Menu.Content>
</Menu.Portal>
</Menu.Root>
PartRequiredResponsibility
Menu.RootyesOwns open state, the item registry, and selection movement.
Menu.TriggeryesA button that toggles the menu and acts as the positioning anchor and focus-restore target.
Menu.PortalyesRenders the surface into a ScreenGui outside the local tree.
Menu.ContentyesThe positioned, focus-trapped, dismissable, motion-driven surface.
Menu.ItemyesA selectable action that registers for ordered movement and emits onSelect.
Menu.GroupnoA vertical layout container that visually groups related items.
Menu.LabelnoA non-interactive heading for a group or section.
Menu.SeparatornoA thin divider between items or groups.

Example

A controlled menu with a grouped action list, a separator, and a destructive item that closes on selection.

RowActionsMenu.tsx
import { useState } from "@rbxts/react";
import { Menu } from "@lattice-ui/menu";
export function RowActionsMenu() {
const [open, setOpen] = useState(false);
return (
<Menu.Root open={open} onOpenChange={setOpen}>
<Menu.Trigger asChild>
<textbutton Text="Actions" Size={UDim2.fromOffset(120, 36)} />
</Menu.Trigger>
<Menu.Portal>
<Menu.Content placement="bottom" sideOffset={6}>
<frame
AutomaticSize={Enum.AutomaticSize.Y}
BackgroundColor3={Color3.fromRGB(28, 32, 42)}
Size={UDim2.fromOffset(220, 0)}
>
<uilistlayout Padding={new UDim(0, 2)} />
<Menu.Label asChild>
<textlabel Text="Manage" />
</Menu.Label>
<Menu.Group>
<Menu.Item onSelect={() => print("rename")}>
<textbutton Text="Rename" />
</Menu.Item>
<Menu.Item onSelect={() => print("duplicate")}>
<textbutton Text="Duplicate" />
</Menu.Item>
</Menu.Group>
<Menu.Separator />
<Menu.Item onSelect={() => print("delete")}>
<textbutton Text="Delete" TextColor3={Color3.fromRGB(244, 120, 120)} />
</Menu.Item>
</frame>
</Menu.Content>
</Menu.Portal>
</Menu.Root>
);
}

How it behaves

Open state

Menu.Root is controllable. Pass open and onOpenChange to control it, or defaultOpen to run uncontrolled (defaults to closed). Menu.Trigger toggles the open state on activation (and on the Return/Space keys). Selecting an item closes the menu unless the item’s onSelect calls preventDefault.

Positioning

Menu.Content is positioned by the popper foundation, anchored to the trigger. It measures the trigger and the content, resolves a final placement, and flips to the opposite side when the requested side would collide with the screen edge. Tune it with placement ("top" | "bottom" | "left" | "right", default "bottom"), sideOffset (gap from the trigger, default 0), alignOffset (shift along the cross axis, default 0), and collisionPadding (minimum distance from the screen edge, default 8).

Focus and selection

When the menu opens, the first item is focused automatically and selection is trapped inside the content (Menu is modal by default). Menu.Item registers itself with the root in render order, and the registry drives ordered movement: pressing Up/Down on a focused item moves selection to the previous or next enabled item, wrapping through the list. Disabled items are skipped. When the menu closes, focus is restored to the trigger.

Menu.Item activates on click or on the Return/Space keys, calling onSelect with an event whose preventDefault() keeps the menu open. The default item renders a left-aligned textbutton that highlights on pointer enter and on gamepad selection.

Dismissal

Menu.Content participates in dismissable-layer behavior. Because Menu is modal, interaction behind the surface is blocked and an outside press dismisses it. Use onPointerDownOutside and onInteractOutside to observe or veto those interactions before the menu closes.

Motion and presence

Menu.Content runs a default popper-aware canvas-group reveal/exit recipe that animates from the resolved placement, so it animates in and out without extra setup. Override it with transition, and pass forceMount to keep the content mounted through its exit animation (useful when you drive motion yourself or need the node to persist).

API reference

Prop Type Description
open boolean Controlled open state. Pair with onOpenChange.
defaultOpen boolean Initial open state for uncontrolled usage. Defaults to false.
onOpenChange (open: boolean) => void Called whenever the open state changes.
modal boolean When true, traps selection inside the menu and blocks interaction behind it. Defaults to true.
children React.ReactNode The menu parts.
Prop Type Description
asChild boolean Merge behavior onto the single child element instead of rendering the default textbutton.
disabled boolean Prevents the trigger from toggling the menu and removes it from selection.
children React.ReactElement The element to render. Required when asChild is set.
Prop Type Description
container BasePlayerGui Target PlayerGui to render the surface into. Defaults to the surrounding portal context's container.
displayOrderBase number Base DisplayOrder for the generated ScreenGui, used to order it against other layers. Defaults to the surrounding portal context's value.
children React.ReactNode The content part.
Prop Type Description
placement "top" | "bottom" | "left" | "right" Requested side to position the content on. Flips on collision. Defaults to "bottom".
sideOffset number Gap in pixels between the trigger and the content. Defaults to 0.
alignOffset number Shift in pixels along the trigger's cross axis. Defaults to 0.
collisionPadding number Minimum distance in pixels to keep from the screen edge. Defaults to 8.
asChild boolean Render the single child element inside the positioned wrapper instead of the default canvasgroup contents.
forceMount boolean Keeps the content mounted while exit motion runs.
transition PresenceMotionConfig Overrides the default popper-aware canvas-group reveal/exit recipe.
onPointerDownOutside (event: LayerInteractEvent) => void Called when a pointer press occurs outside the content, before dismissal.
onInteractOutside (event: LayerInteractEvent) => void Called for any other outside interaction, before dismissal.
children React.ReactNode The menu contents.
Prop Type Description
asChild boolean Merge item behavior onto the single child element instead of rendering the default textbutton.
disabled boolean Prevents selection and skips the item during ordered movement.
onSelect (event: MenuSelectEvent) => void Called on activation. Call event.preventDefault() to keep the menu open.
children React.ReactElement The element to render. Required when asChild is set.
Prop Type Description
asChild boolean Merge the group onto the single child element instead of rendering the default vertical-layout frame.
children React.ReactElement The grouped items to render. Required when asChild is set.
Prop Type Description
asChild boolean Merge the label onto the single child element instead of rendering the default textlabel.
children React.ReactElement The label element to render. Required when asChild is set.
Prop Type Description
asChild boolean Merge the separator onto the single child element instead of rendering the default 1px divider frame.
children React.ReactElement The divider element to render. Required when asChild is set.