@lattice-ui/select currently single-value only
What It Is For
Use Select when the UI needs a compact single-choice picker with caller-owned trigger, value display, and overlay visuals.
It works well when options should open into a managed list but the user should not type freeform search text.
import { PortalProvider } from "@lattice-ui/layer";
import { Select } from "@lattice-ui/select";
import React from "@rbxts/react";
type Props = {
playerGui: PlayerGui;
};
function SelectDemo() {
const [value, setValue] = React.useState("apac");
return (
<frame BackgroundTransparency={1} Size={UDim2.fromOffset(360, 220)}>
<Select.Root onValueChange={setValue} value={value}>
<Select.Trigger asChild>
<textbutton
AutoButtonColor={false}
BackgroundColor3={Color3.fromRGB(34, 41, 54)}
BorderSizePixel={0}
Size={UDim2.fromOffset(320, 40)}
Text=""
>
<uicorner CornerRadius={new UDim(0, 8)} />
<Select.Value asChild placeholder="Pick a region">
<textlabel
BackgroundTransparency={1}
Position={UDim2.fromOffset(12, 0)}
Size={UDim2.fromOffset(280, 40)}
TextColor3={Color3.fromRGB(236, 240, 248)}
TextSize={14}
TextXAlignment={Enum.TextXAlignment.Left}
/>
</Select.Value>
</textbutton>
</Select.Trigger>
<Select.Portal>
<Select.Content asChild offset={new Vector2(0, 8)} placement="bottom">
<frame
BackgroundColor3={Color3.fromRGB(22, 28, 39)}
BorderSizePixel={0}
Size={UDim2.fromOffset(320, 126)}
>
<uicorner CornerRadius={new UDim(0, 8)} />
<uipadding
PaddingLeft={new UDim(0, 8)}
PaddingRight={new UDim(0, 8)}
PaddingTop={new UDim(0, 8)}
PaddingBottom={new UDim(0, 8)}
/>
<uilistlayout
FillDirection={Enum.FillDirection.Vertical}
Padding={new UDim(0, 4)}
/>
<Select.Item asChild textValue="Asia Pacific" value="apac">
<textbutton
AutoButtonColor={false}
BackgroundColor3={Color3.fromRGB(34, 41, 54)}
BorderSizePixel={0}
Size={UDim2.fromOffset(304, 30)}
Text="Asia Pacific"
TextColor3={Color3.fromRGB(236, 240, 248)}
TextSize={14}
TextXAlignment={Enum.TextXAlignment.Left}
>
<uicorner CornerRadius={new UDim(0, 6)} />
<uipadding PaddingLeft={new UDim(0, 10)} />
</textbutton>
</Select.Item>
<Select.Item
asChild
disabled={true}
textValue="North America"
value="na"
>
<textbutton
Active={false}
AutoButtonColor={false}
BackgroundColor3={Color3.fromRGB(34, 41, 54)}
BorderSizePixel={0}
Selectable={false}
Size={UDim2.fromOffset(304, 30)}
Text="North America (disabled)"
TextColor3={Color3.fromRGB(140, 148, 162)}
TextSize={14}
TextXAlignment={Enum.TextXAlignment.Left}
>
<uicorner CornerRadius={new UDim(0, 6)} />
<uipadding PaddingLeft={new UDim(0, 10)} />
</textbutton>
</Select.Item>
<Select.Item asChild textValue="Europe" value="eu">
<textbutton
AutoButtonColor={false}
BackgroundColor3={Color3.fromRGB(34, 41, 54)}
BorderSizePixel={0}
Size={UDim2.fromOffset(304, 30)}
Text="Europe"
TextColor3={Color3.fromRGB(236, 240, 248)}
TextSize={14}
TextXAlignment={Enum.TextXAlignment.Left}
>
<uicorner CornerRadius={new UDim(0, 6)} />
<uipadding PaddingLeft={new UDim(0, 10)} />
</textbutton>
</Select.Item>
</frame>
</Select.Content>
</Select.Portal>
</Select.Root>
<textlabel
BackgroundTransparency={1}
Position={UDim2.fromOffset(0, 62)}
Size={UDim2.fromOffset(320, 20)}
Text={`Selected: ${value}`}
TextColor3={Color3.fromRGB(172, 181, 196)}
TextSize={13}
TextXAlignment={Enum.TextXAlignment.Left}
/>
</frame>
);
}
export function SelectExample(props: Props) {
return (
<PortalProvider container={props.playerGui}>
<SelectDemo />
</PortalProvider>
);
} Live demo is experimental and may contain bugs.
Install
Global CLI command: lattice add select
Monorepo local script
Use your package manager wrapper when running the local lattice command.
pnpm lattice add selectPublic Exports
-
Select -
Select.Root -
Select.Trigger -
Select.Value -
Select.Portal -
Select.Content -
Select.Item -
Select.Group -
Select.Label -
Select.Separator -
SelectContent -
SelectGroup -
SelectItem -
SelectLabel -
SelectPortal -
SelectRoot -
SelectSeparator -
SelectTrigger -
SelectValue
State Model
Select.Rootmanages controlled or uncontrolledvalueandopenstate.- Items register in the root context, and the selected value resolves back to item text for
Select.Valuedisplay. - If the current value no longer points at an enabled item, the root falls back to the first enabled registered item.
Key API
Select.Root
Use value, defaultValue, onValueChange, open, defaultOpen, and onOpenChange to connect the picker to app state.
Select.Value
Use placeholder when the control should show an explicit empty-state label before a value is chosen.
Select.Content
Use placement, offset, padding, and outside-interaction hooks for overlay geometry and dismissal.
Select.Item
Every item needs a stable value; use textValue when the rendered child text is not the same as the semantic value label.
Composition Patterns
Labeled trigger + portal content
Render the field shell in the trigger, then mount content in a portal so placement and dismissal stay reliable.
Small enumerated settings
Use Select when the option set is longer than a radio group but still small enough to browse without search.
Cautions / Limits
- Current public behavior is single-value only; use
Combobox,Checkbox, orToggleGroupwhen the selection model is different. - Overlay behavior is portal-based, so set up
PortalProvidernear the app root for predictable mounting and stacking. - Keep content mounted through presence-aware exit motion; do not unmount the portal immediately if an exit transition should complete.
Decision Guides
- Choosing between controls
Compare Select vs Combobox vs Radio Group for compact single-choice workflows.
- Overlay and state pitfalls
Review PortalProvider, disabled items, and controlled/uncontrolled transitions before composing lists.