@lattice-ui/select Feature limited
import Select
depends on core , focus , layer , motion , popper Select is the primitive for picking one value from a list: difficulty pickers, region menus, sort dropdowns, and any “choose one” control. It coordinates open state, the selected value, item registration, popper positioning, and outside-press dismissal so your component only has to render a trigger, a value label, and the items.
Reach for Select when a control needs to hold a single value, open an anchored popup over a trigger, and dismiss predictably on selection or an outside interaction.
Import
import { Select } from "@lattice-ui/select";Anatomy
Root, Trigger, Portal, Content, and at least one Item form the minimum useful select. Value, Group, Label, and Separator are optional and help you structure the trigger label and the list.
Select anatomy
<Select.Root> <Select.Trigger> <Select.Value /> </Select.Trigger> <Select.Portal> <Select.Content> <Select.Group> <Select.Label /> <Select.Item value="..." /> <Select.Separator /> <Select.Item value="..." /> </Select.Group> </Select.Content> </Select.Portal></Select.Root>| Part | Required | Responsibility |
|---|---|---|
Select.Root | yes | Owns open + value state and the item registry, shared through context. |
Select.Trigger | yes | A button that toggles the content open and closed. |
Select.Value | no | Renders the selected item’s text, or a placeholder when nothing is chosen. |
Select.Portal | yes | Renders the content into a ScreenGui outside the local tree. |
Select.Content | yes | The popper-positioned, dismissable list surface. |
Select.Item | yes | A selectable option that registers itself and sets the value on activation. |
Select.Group | no | A non-semantic container for grouping related items. |
Select.Label | no | A heading for a group. |
Select.Separator | no | A thin divider between items or groups. |
Example
A controlled select for a match difficulty, with a placeholder, a labelled group, and a disabled option.
import { useState } from "@rbxts/react";import { Select } from "@lattice-ui/select";
export function DifficultySelect() { const [value, setValue] = useState<string>();
return ( <Select.Root value={value} onValueChange={setValue}> <Select.Trigger> <Select.Value placeholder="Choose difficulty" /> </Select.Trigger>
<Select.Portal> <Select.Content placement="bottom" sideOffset={6}> <frame AutomaticSize={Enum.AutomaticSize.Y} BackgroundColor3={Color3.fromRGB(47, 53, 68)} BorderSizePixel={0} Size={UDim2.fromOffset(220, 0)} > <uilistlayout SortOrder={Enum.SortOrder.LayoutOrder} />
<Select.Group> <Select.Label> <textlabel BackgroundTransparency={1} Size={UDim2.fromOffset(220, 20)} Text="Standard" TextColor3={Color3.fromRGB(168, 176, 191)} TextSize={13} /> </Select.Label>
<Select.Item value="easy" textValue="Easy" /> <Select.Item value="normal" textValue="Normal" /> <Select.Separator /> <Select.Item value="hard" textValue="Hard" /> <Select.Item value="nightmare" textValue="Nightmare" disabled /> </Select.Group> </frame> </Select.Content> </Select.Portal> </Select.Root> );}This release of Select is single-value only. There is no multi-select mode, and value/defaultValue are a single string, not an array. Track multiple selections with separate controls or your own state until multi-select lands.
How it behaves
Open state
Select.Root is controllable. Pass open and onOpenChange to control it, or defaultOpen to run uncontrolled (defaults to false). Select.Trigger toggles open on activation and on Return/Space, and selecting an item closes it. When the root is disabled, the trigger cannot open the content.
Value and selection
The selected value is a single string. Select.Root is controllable via value/onValueChange, or uncontrolled via defaultValue. Each Select.Item registers itself with the root on mount — recording its value, textValue, disabled state, and document order — so the root always knows the full ordered set of options. Activating an item (click, Return, or Space) sets the value and closes the content. Selecting a disabled item is ignored, and if the current value ever points at a disabled or missing item, the root falls back to the first enabled item.
The value label
Select.Value reads the current value from context and renders the matching item’s textValue (falling back to the raw value, then to placeholder when nothing is selected). Because the label resolves through the item registry, it stays correct without you wiring text manually.
Positioning
Select.Content is positioned with popper, anchored to the trigger. Control the side with placement ("top" | "bottom" | "left" | "right", defaulting to "bottom"), nudge it with sideOffset and alignOffset, and keep it inside the viewport with collisionPadding. The content is measured and flipped automatically when it would collide with an edge.
Dismissal
Select.Content participates in dismissable-layer behavior in non-modal mode: interaction behind it is not blocked, but an outside press closes it. Use onPointerDownOutside and onInteractOutside to observe those interactions before the content dismisses.
Motion and presence
Select.Content runs a default popper entrance/exit recipe — a canvas-group reveal by default, or a transform-based reveal when asChild is set. Override it with transition, and pass forceMount to keep the content mounted through its exit animation.
Select.Portal renders into a ScreenGui on the player’s PlayerGui, not the local component tree; use container and displayOrderBase to target a specific GUI and order it against other layers. Items respond to gamepad SelectionGained/SelectionLost for hover state, but Select does not install Roblox native directional selection or keyboard navigation between items — wire GuiObject.Selectable and selection order yourself if you need gamepad list traversal.
API reference
Select.Root
| Prop | Type | Description |
|---|---|---|
| value | string | Controlled selected value. Pair with onValueChange. |
| defaultValue | string | Initial value for uncontrolled usage. |
| onValueChange | (value: string) => void | Called when the selected value changes. |
| open | boolean | Controlled open state. Pair with onOpenChange. |
| defaultOpen | boolean | Initial open state for uncontrolled usage. Defaults to false. |
| onOpenChange | (open: boolean) => void | Called when the open state changes. |
| disabled | boolean | Disables the whole select: the trigger cannot open and values cannot change. Defaults to false. |
| required | boolean | Marks the select as required; surfaced through context for consumer use. Defaults to false. |
| children | React.ReactNode | The select parts. |
Select.Trigger
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Merge trigger behavior onto the single child element instead of rendering the default textbutton. |
| disabled | boolean | Prevents this trigger from opening the select, in addition to the root's disabled state. Defaults to false. |
| children | React.ReactElement | The element to render. Required when asChild is set; otherwise rendered inside the default button (e.g. a Select.Value). |
Select.Value
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Merge the resolved text onto the single child element instead of rendering the default textlabel. |
| placeholder | string | Text shown when no value is selected. Defaults to an empty string. |
| children | React.ReactElement | The element to render. Required when asChild is set. |
Select.Portal
| Prop | Type | Description |
|---|---|---|
| container | BasePlayerGui | Target PlayerGui to render the content 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. |
Select.Content
| Prop | Type | Description |
|---|---|---|
| placement | "top" | "bottom" | "left" | "right" | Preferred side to anchor the content against the trigger. Defaults to "bottom". |
| sideOffset | number | Gap between the trigger and the content along the placement axis. |
| alignOffset | number | Offset along the cross axis from the aligned edge. |
| collisionPadding | number | Minimum distance to keep from the viewport edges when repositioning. |
| transition | PresenceMotionConfig | Overrides the default popper reveal/exit recipe. |
| forceMount | boolean | Keeps the content mounted while exit motion runs. |
| asChild | boolean | Render onto the single child element instead of the default canvasgroup; also switches the default recipe to a transform-based reveal. |
| 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 list surface contents. |
Select.Item
| Prop | Type | Description |
|---|---|---|
| value * | string | The value this item selects when activated. |
| textValue | string | Text used for the value label and the default item button. Defaults to value. |
| disabled | boolean | Prevents selection and removes the item from value resolution. Defaults to false. |
| asChild | boolean | Merge item behavior onto the single child element instead of rendering the default textbutton. |
| children | React.ReactElement | The element to render. Required when asChild is set. |
Select.Group
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Merge onto the single child element instead of rendering the default frame container. |
| children | React.ReactElement | The grouped items (and optional label). |
Select.Label
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Merge onto the single child element instead of rendering the default textlabel. |
| children | React.ReactElement | The label element to render. Required when asChild is set. |
Select.Separator
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Merge 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. |