@lattice-ui/combobox Stable direction
import Combobox
depends on core , layer , motion , popper Combobox is the primitive for a filterable, single-value picker: searchable selects, command palettes, and autocomplete fields. It coordinates value state, input text, query filtering, item registration, positioning, and dismissal so your component only has to render the field, the listbox, and the items.
Reach for Combobox when a select needs type-to-filter behavior: an input narrows a registered list of items by text, the user picks one, and the chosen value drives the field. Combobox owns three pieces of state at once — the selected value, the input text, and the open state — and keeps them in sync, falling back to a forced selection so the field is never left in an invalid state.
Import
import { Combobox } from "@lattice-ui/combobox";Anatomy
Compose the parts you need. Root, Portal, Content, and Item form the working picker. Use either Input (type-to-filter) or Trigger + Value (toggle and display) as the anchor, and Group, Label, and Separator to structure longer lists.
<Combobox.Root> <Combobox.Trigger> <Combobox.Value /> </Combobox.Trigger> <Combobox.Input /> <Combobox.Portal> <Combobox.Content> <Combobox.Label /> <Combobox.Group> <Combobox.Item value="..." /> </Combobox.Group> <Combobox.Separator /> <Combobox.Item value="..." /> </Combobox.Content> </Combobox.Portal></Combobox.Root>| Part | Required | Responsibility |
|---|---|---|
Combobox.Root | yes | Owns value, input, and open state, plus the item registry and filter function. |
Combobox.Input | no | A TextBox that drives the query and opens the list as the user types. Acts as the anchor. |
Combobox.Trigger | no | A button that toggles the list and acts as the anchor when no input is present. |
Combobox.Value | no | A label that displays the selected item’s text (or a placeholder). |
Combobox.Portal | yes | Renders the listbox into a ScreenGui outside the local tree. |
Combobox.Content | yes | The positioned, dismissable, motion-driven listbox. |
Combobox.Item | yes | A selectable option that registers its value and text, and hides when filtered out. |
Combobox.Group | no | A container that visually groups related items. |
Combobox.Label | no | A non-interactive heading for a group or section. |
Combobox.Separator | no | A thin divider between items or groups. |
Example
A controlled combobox with a filtering input. Typing narrows the list; selecting an item sets the value and syncs the input.
import { useState } from "@rbxts/react";import { Combobox } from "@lattice-ui/combobox";
const WORLDS = ["Aurora", "Basalt", "Cinder", "Driftwood", "Ember"];
export function WorldPicker() { const [value, setValue] = useState<string>();
return ( <Combobox.Root value={value} onValueChange={setValue}> <Combobox.Input placeholder="Search worlds" />
<Combobox.Portal> <Combobox.Content sideOffset={4}> <frame AutomaticSize={Enum.AutomaticSize.Y} BackgroundColor3={Color3.fromRGB(28, 32, 42)} Size={UDim2.fromOffset(240, 0)} > <uilistlayout Padding={new UDim(0, 2)} /> <Combobox.Label asChild> <textlabel Text="Worlds" /> </Combobox.Label> {WORLDS.map((world) => ( <Combobox.Item key={world} value={world}> <textbutton Text={world} /> </Combobox.Item> ))} </frame> </Combobox.Content> </Combobox.Portal> </Combobox.Root> );}Omit value/onValueChange (and inputValue/open) and pass defaultValue, defaultInputValue, or defaultOpen instead to let Combobox own its state. Each of value, input text, and open state can be controlled independently — control only the pieces something outside the combobox needs to drive.
How it behaves
Open state
Combobox.Root is controllable. Pass open and onOpenChange to control the listbox, or defaultOpen to run uncontrolled (defaults to closed). Combobox.Trigger toggles the list on activation (and on Return/Space), and typing in Combobox.Input opens it automatically. Selecting an item closes the list.
Value and input state
The selected value and the input text are separate, independently controllable pieces of state. Selecting an item sets the value and syncs the input to that item’s display text. Typing in the input updates the query (and opens the list) without changing the value until a selection is made; when the list closes, the input is re-synced from the current value so the field always shows the selected item. Set required to force a selection: while the list is open, Combobox keeps a valid enabled item selected, falling back to the first enabled item rather than leaving the value empty.
Combobox.Value displays the selected item’s text, resolved from the item registry, or its placeholder when nothing is selected. disabled blocks all state changes; readOnly blocks input edits but still allows selection through items.
Filtering
Combobox.Item declares a value and an optional textValue (the text matched and displayed; defaults to value). The root’s filterFn decides whether each item matches the current query — the default does a case-insensitive substring match. Items that do not match are hidden and made non-interactive, so the list shows only what fits the query. Provide your own filterFn for fuzzy matching, token search, or locale-aware comparison.
Positioning
Combobox.Content is positioned by the popper foundation against the active anchor — the input when present, otherwise the trigger. It flips to the opposite side on collision. Tune it with placement ("top" | "bottom" | "left" | "right", default "bottom"), sideOffset (gap from the anchor, default 0), alignOffset (shift along the cross axis, default 0), and collisionPadding (minimum distance from the screen edge, default 8).
Dismissal
Combobox.Content participates in dismissable-layer behavior and is always non-modal, so the rest of the UI stays interactive while the list is open. The trigger and input are registered as inside refs, so interacting with them does not dismiss the list. Any other outside press closes it. Use onPointerDownOutside and onInteractOutside to observe or veto those interactions before the list closes.
Motion and presence
Combobox.Content runs a default popper-aware canvas-group reveal/exit recipe that animates from the resolved placement, so the list 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).
Combobox tracks value, input text, and open state separately and reconciles them automatically: selecting syncs the input, closing re-syncs the input from the value, and required forces a valid selection while open. Control each piece only when you need to — mixing controlled value with uncontrolled input is fully supported.
API reference
Combobox.Root
| Prop | Type | Description |
|---|---|---|
| value | string | Controlled selected value. Pair with onValueChange. |
| defaultValue | string | Initial selected value for uncontrolled usage. |
| onValueChange | (value: string) => void | Called whenever the selected value changes. |
| inputValue | string | Controlled input text. Pair with onInputValueChange. |
| defaultInputValue | string | Initial input text for uncontrolled usage. Defaults to an empty string. |
| onInputValueChange | (inputValue: string) => void | Called whenever the input text changes. |
| open | boolean | Controlled open state of the listbox. Pair with onOpenChange. |
| defaultOpen | boolean | Initial open state for uncontrolled usage. Defaults to false. |
| onOpenChange | (open: boolean) => void | Called whenever the open state changes. |
| disabled | boolean | Disables the whole combobox, blocking opening, input edits, and selection. Defaults to false. |
| readOnly | boolean | Blocks input edits while still allowing selection through items. Defaults to false. |
| required | boolean | Forces a valid enabled item to stay selected while the list is open. Defaults to false. |
| filterFn | (itemText: string, query: string) => boolean | Decides whether an item matches the query. Defaults to a case-insensitive substring match. |
| children | React.ReactNode | The combobox parts. |
Combobox.Input
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Merge input behavior onto the single child element instead of rendering the default textbox. |
| disabled | boolean | Disables the input. Combined with the root's disabled state. |
| readOnly | boolean | Blocks edits to the input text. Combined with the root's readOnly state. |
| placeholder | string | Placeholder text shown when the input is empty. Defaults to "Type to filter". |
| children | React.ReactElement | The element to render. Required when asChild is set. |
Combobox.Trigger
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Merge trigger behavior onto the single child element instead of rendering the default textbutton. |
| disabled | boolean | Prevents the trigger from toggling the list. Combined with the root's disabled state. |
| children | React.ReactElement | The element to render. Required when asChild is set. |
Combobox.Value
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Merge the resolved value 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. |
Combobox.Portal
| Prop | Type | Description |
|---|---|---|
| container | BasePlayerGui | Target PlayerGui to render the listbox 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. |
Combobox.Content
| Prop | Type | Description |
|---|---|---|
| placement | "top" | "bottom" | "left" | "right" | Requested side to position the listbox on. Flips on collision. Defaults to "bottom". |
| sideOffset | number | Gap in pixels between the anchor and the listbox. Defaults to 0. |
| alignOffset | number | Shift in pixels along the anchor'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 listbox 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 listbox, before dismissal. |
| onInteractOutside | (event: LayerInteractEvent) => void | Called for any other outside interaction, before dismissal. |
| children | React.ReactNode | The listbox contents. |
Combobox.Item
| Prop | Type | Description |
|---|---|---|
| value | string | Required. The value selected when this item is chosen. |
| textValue | string | Text used for filtering and display. Defaults to value. |
| disabled | boolean | Prevents selection and excludes the item from forced selection. |
| 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. |
Combobox.Group
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Merge the group onto the single child element instead of rendering the default container frame. |
| children | React.ReactElement | The grouped items to render. Required when asChild is set. |
Combobox.Label
| 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. |
Combobox.Separator
| 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. |