Combobox
View as MarkdownAn input with a popup listbox for searching and selecting values from a collection.
Features
Permalink to heading "Features"- Searchable listbox with keyboard navigation
- Supports single and multiple selection
- Supports disabled items and item groups
- Works with the Field component for forms and validation
- Submits the selected item's
valueon form submit, not its label - the visible input only ever displays text,hiddenInputcarries the real value(s), or an empty one while nothing is selected - Supports an
emptystate placeholder shown automatically whenever no items match - Filtering is plain
onInputValueChangeuserland code, not a special API - swap the match logic per instance the same way you'd swap it in any Zag/Ark UI consumer - Ships with locale-aware substring filtering via Zag's i18n utilities, wired up in the default mount entry
- Supports async, server-rendered search results via
ui:template - Can move the input into the popup, behind a focusable trigger, with the
dialogpopup type
Unlike Select, Zag's combobox machine renders no native form control of its own - the visible input only ever holds the highlighted item's label (or, with allowCustomValue, arbitrary typed text), never its value. hiddenInput fills that gap: one visually hidden text input (kept out of the accessibility tree and tab order via aria-hidden/tabindex="-1", not type="hidden") per selected value, kept in sync with the current selection so the item's value (not its label) is what actually gets submitted. Include it once anywhere inside combobox.root - it renders as many hidden inputs as there are selected values (one, or - with multiple - several), and a single empty one while nothing is selected, like a native input would submit its name with an empty value. That empty input is also what a Field moves the focus through when the combobox is the first invalid field of a submitted form: it hands the focus on to the visible input.
Zag puts the listbox semantics and scrolling on list, so the options belong inside list rather than directly inside content, which is a plain wrapper around it (a dialog, with the dialog popup type). The styled ui:combobox.content is the positioner and content in one part, and ui:combobox.list is the list - you place it inside the content yourself.
combobox.empty renders a placeholder for when the collection has no items to show - whether that's because nothing matches the current search text, or because an async search hasn't returned results yet. It's shown/hidden automatically alongside content/list's own data-empty attribute, so no wiring is needed beyond placing it next to combobox.list inside content - it isn't an option, so it doesn't belong inside the listbox.
<primitives:combobox.root>
<primitives:combobox.label />
<primitives:combobox.control>
<primitives:combobox.input />
<primitives:combobox.clearTrigger />
<primitives:combobox.trigger />
</primitives:combobox.control>
<primitives:combobox.positioner>
<primitives:combobox.content>
<primitives:combobox.empty />
<primitives:combobox.list>
<primitives:combobox.item>
<primitives:combobox.itemText />
<primitives:combobox.itemIndicator />
</primitives:combobox.item>
<primitives:combobox.itemGroup>
<primitives:combobox.itemGroupLabel />
</primitives:combobox.itemGroup>
</primitives:combobox.list>
</primitives:combobox.content>
</primitives:combobox.positioner>
<primitives:combobox.hiddenInput />
</primitives:combobox.root>
Installation
Permalink to heading "Installation"typo3 ui:add combobox
Please copy the files manually from GitHub into your project.
Read more about installing Components and Primitives.
Examples
Permalink to heading "Examples"Default Value
Permalink to heading "Default Value"Set an initial selected value and render its label into the input on first paint.
Disabled Items
Permalink to heading "Disabled Items"Mark specific options as unavailable.
With Item Groups
Permalink to heading "With Item Groups"Organize items into labeled groups.
With Form Field
Permalink to heading "With Form Field"Use the combobox inside Field to share label, name, required and invalid state.
Multiple Selection
Permalink to heading "Multiple Selection"Allow users to select multiple values from the combobox.
Custom Filter Logic
Permalink to heading "Custom Filter Logic"Override the match logic per instance by passing your own onInputValueChange to a custom mount entry - swap contains for startsWith, or filter however you like.
Async Search
Permalink to heading "Async Search"Load items from a server-side search endpoint as the user types, instead of rendering the full collection up front.
Author the item's markup once inside a ui:template block - it makes ui:ref work on plain, hand-authored elements even though they're technically slot content, not a component's own template body. combobox.item, combobox.itemText, and combobox.itemIndicator all detect they're inside a ui:template block automatically - so we dont need to pass a value prop. On the client, clone the template per search result, populate its ui:ref'd elements directly, and rebuild the collection. Fetching, debouncing, and race-condition handling are left to your own code, typically built on @zag-js/async-list.
collection can be omitted entirely for a combobox with no server-known items at all - it's optional and defaults to empty regardless of searchUrl.
The status placeholder itself is just combobox.empty - Combobox already shows/hides it automatically whenever there are no items rendered, whatever the reason (no query typed yet, a request in flight, a failed request, or a query with zero matches). Only its content - a spinner and a status text - is something the example's own code owns and updates; the loading/error messaging is entirely up to you. Both are plain, hand-authored elements passed {ui:ref(name: 'statusSpinner', context: 'combobox')} - since they're slot content rather than a component's own template body, ui:ref needs the explicit context argument to know which ancestor component to attach to, the same way ui:template's own context argument does for the item markup below. The example below sends the search query via extbase.post() rather than a GET param, sidestepping a cHash mismatch f:uri.action's URL would otherwise hit, and drives the spinner/status text off a single DelayedIndicator so they can never disagree.
Async Search with Groups
Permalink to heading "Async Search with Groups"Async results can be grouped too - author a second ui:template for the group wrapper (combobox.itemGroup/combobox.itemGroupLabel), clone one per group returned by your search, and append the item clones into it instead of directly into combobox.list.
combobox.itemGroup needs a unique data-id per instance so Combobox can tell groups apart - the same thing ui:id() gives a server-rendered group, done client-side with uid(). Nothing about Combobox's own rendering needed to change for this: it already looks up every itemGroup part independently and reads its data-id fresh on every render, whether that element was server-rendered or just cloned.
Input in Content
Permalink to heading "Input in Content"By default the input is the combobox's focus target and the popup is a passive listbox. Set popupType to PopupType::Dialog (popupType="{f:constant(name: 'Jramke\FluidPrimitives\Enum\PopupType::Dialog')}") to turn it into a popover-style picker instead: the input moves into the content, above the list, and a trigger opens the popup. The content is then announced as a dialog, a click on the label focuses the trigger, and the trigger becomes focusable - it is the only way in, so Zag makes it so by default (the trigger's own focusable prop overrides that either way).
Since the input is no longer in the control, the trigger has to show the current selection itself - here a plain button with a hand-authored valueText ref that the example's entry file keeps in sync with api.valueAsString.
Localization
Permalink to heading "Localization"Default combobox trigger labels are shipped via XLF and follow the current Site Language. For per-template overrides, pass translated strings through the translations prop.
<f:variable
name="comboboxTranslations"
value="{
triggerLabel: '{f:translate(key: \'LLL:EXT:site_package/Resources/Private/Language/locallang.xlf:combobox.trigger\')}',
clearTriggerLabel: '{f:translate(key: \'LLL:EXT:site_package/Resources/Private/Language/locallang.xlf:combobox.clear\')}'
}"
/>
<ui:combobox.root translations="{comboboxTranslations}"> ... </ui:combobox.root>
API Reference
Permalink to heading "API Reference"The following tables cover the available props of the Fluid Primitives. For a full list of available client side props and methods, see the Zag.js Machine API.
combobox.root
Permalink to heading "combobox.root"Provides shared combobox state and wraps all related parts. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
collection | Domain\Dto\ListCollectionThe item collection. | Yes | - |
name | stringThe `name` attribute of the underlying select. Inherited from a surrounding field when available. | No | - |
form | stringThe associate form of the underlying select. | No | - |
disabled | booleanWhether the select is disabled. Inherited from a surrounding field when available. | No | - |
invalid | booleanWhether the select is invalid. Inherited from a surrounding field when available. | No | - |
readOnly | booleanWhether the select is read-only. Inherited from a surrounding field when available. | No | - |
required | booleanWhether the select is required. Inherited from a surrounding field when available. | No | - |
placeholder | stringThe placeholder text of the combobox's input. | No | - |
defaultOpen | booleanThe initial open state of the combobox when rendered. Use when you don't need to control the open state of the combobox. | No | false |
defaultValue | mixedThe initial value of the combobox's selected items when rendered. Use when you don't need to control the value of the combobox's selected items. | No | - |
defaultInputValue | stringThe initial value of the combobox's input when rendered. Use when you don't need to control the value of the combobox's input. | No | - |
defaultHighlightedValue | stringThe initial highlighted value of the combobox when rendered. Use when you don't need to control the highlighted value of the combobox. | No | - |
openOnClick | booleanWhether to open the combobox popup on initial click on the input. | No | false |
openOnKeyPress | booleanWhether to open the combobox on arrow key press. | No | true |
openOnChange | booleanWhether to show the combobox when the input value changes. | No | true |
closeOnSelect | booleanWhether to close the combobox when an item is selected. Defaults to `true` or if multiple enabled `false`. | No | - |
loopFocus | booleanWhether to loop focus within the combobox. | No | true |
multiple | booleanWhether to allow multiple selection. **Good to know:** When `multiple` is `true`, the `selectionBehavior` is automatically set to `clear`. It is recommended to render the selected items in a separate container. | No | - |
allowCustomValue | booleanWhether to allow typing custom values in the input. | No | false |
alwaysSubmitOnEnter | booleanWhether to always submit on Enter key press, even if popup is open. Useful for single-field autocomplete forms where Enter should submit the form. | No | false |
inputBehavior | Enum\ComboboxInputBehaviorDefines the auto-completion behavior of the combobox. - `autohighlight`: The first focused item is highlighted as the user types - `autocomplete`: Navigating the listbox with the arrow keys selects the item and the input is updated. | No | None |
selectionBehavior | Enum\ComboboxSelectionBehaviorThe behavior of the combobox input when an item is selected - `replace`: The selected item string is set as the input value - `clear`: The input value is cleared - `preserve`: The input value is preserved. Defaults to `replace` or if multiple enabled `clear` | No | - |
popupType | Enum\PopupTypeThe kind of popup the combobox's content is: `listbox` for a regular combobox, `dialog` when it holds other interactive content. | No | Listbox |
autoFocus | booleanWhether to autofocus the input on mount. | No | - |
positioning | arrayThe positioning options to dynamically position the menu. | No | {
"placement": "bottom",
"sameWidth": true
} |
translations | arrayLocalized combobox labels. Use `f:translate` for per-template localization overrides when needed. | No | - |
searchUrl | stringEndpoint queried for async search results (e.g. built with f:uri.action in the consuming template). The consumer's own controller/entry.ts owns the request/response shape. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
rootId | stringThe root ID of the component, used for hydration and identification. | No | - |
ids | arrayThe IDs of of the component parts for composition. | No | [] |
autoMount | booleanWhether the client initializes the component on its own. Set it to false to mount it yourself with `mount()` | No | true |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-root | <uid> |
data-invalid | Present when invalid |
data-readonly | Present when read-only |
combobox.label
Permalink to heading "combobox.label"Labels the combobox. Renders a <label> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-label | <uid> |
data-readonly | Present when read-only |
data-disabled | Present when disabled |
data-invalid | Present when invalid |
data-required | Present when required |
data-focus | Present when focused |
combobox.control
Permalink to heading "combobox.control"Groups the input and its trigger/clear buttons. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-control | <uid> |
data-state | "open" | "closed" |
data-focus | Present when focused |
data-disabled | Present when disabled |
data-invalid | Present when invalid |
combobox.input
Permalink to heading "combobox.input"The text input used to search and select from the collection. Renders an <input> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-input | <uid> |
data-invalid | Present when invalid |
data-autofocus | |
data-state | "open" | "closed" |
combobox.clearTrigger
Permalink to heading "combobox.clearTrigger"Clears the current input value and selection. Renders a <button> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-clear-trigger | <uid> |
data-invalid | Present when invalid |
combobox.trigger
Permalink to heading "combobox.trigger"Opens and closes the combobox listbox. Not focusable unless focusable is set, or popupType is dialog. Renders a <button> element.
| Name | Description | Required | Default |
|---|---|---|---|
focusable | booleanWhether the trigger can receive focus. By default only the trigger of a combobox with `popupType` `dialog` can - otherwise the input is the focus target and the trigger stays out of the tab order. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-trigger | <uid> |
data-state | "open" | "closed" |
data-invalid | Present when invalid |
data-focusable | |
data-readonly | Present when read-only |
data-disabled | Present when disabled |
combobox.positioner
Permalink to heading "combobox.positioner"Positions the floating combobox content. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-positioner | <uid> |
data-nested | Present when the positioner is nested within another {{widget}} |
data-has-nested | Present when the positioner has nested {{widget}}s |
combobox.content
Permalink to heading "combobox.content"The popup surface, positioned by positioner. Holds the list and empty and, with popupTypedialog, the input too. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-content | <uid> |
data-state | "open" | "closed" |
data-placement | The placement of the content |
data-side | The side of the trigger that the content is positioned on |
data-empty | Present when the content is empty |
data-nested | listbox |
data-has-nested | listbox |
combobox.list
Permalink to heading "combobox.list"The listbox inside content that holds the options - it scrolls and carries role="listbox". Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-list | <uid> |
data-empty | Present when the content is empty |
combobox.empty
Permalink to heading "combobox.empty"Displays a placeholder for when no items match the current search. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
combobox.item
Permalink to heading "combobox.item"Represents a selectable option. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
item | mixedThe collection item for this combobox option. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-item | <uid> |
data-highlighted | Present when highlighted |
data-state | "checked" | "unchecked" |
data-disabled | Present when disabled |
data-value | The value of the item |
combobox.itemText
Permalink to heading "combobox.itemText"Displays the text content of an option. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-item-text | <uid> |
data-state | "checked" | "unchecked" |
data-disabled | Present when disabled |
data-highlighted | Present when highlighted |
combobox.itemIndicator
Permalink to heading "combobox.itemIndicator"Displays the selected-state indicator for an option. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-item-indicator | <uid> |
data-state | "checked" | "unchecked" |
combobox.itemGroup
Permalink to heading "combobox.itemGroup"Groups related options together. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
value | stringThe value identifier of the item group. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-combobox-item-group | <uid> |
data-empty | Present when the content is empty |
combobox.itemGroupLabel
Permalink to heading "combobox.itemGroupLabel"Labels a group of related options. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
combobox.hiddenInput
Permalink to heading "combobox.hiddenInput"Provides the real, submittable value(s) for the selected item(s), since the visible input only ever holds label text. Renders one <input> element per selected value, or one empty <input> while nothing is selected.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Machine JavaScript API
Permalink to heading "Machine JavaScript API"| Name | Type | Description |
|---|---|---|
focused | boolean | Whether the combobox is focused |
open | boolean | Whether the combobox is open |
inputValue | string | The value of the combobox input |
highlightedValue | string | null | The value of the highlighted item |
highlightedItem | V | null | The highlighted item |
setHighlightValue | (value: string) => void | The value of the combobox input |
clearHighlightValue | VoidFunction | Function to clear the highlighted value |
syncSelectedItems | VoidFunction | Function to sync the selected items with the value. Useful when `value` is updated from async sources. |
selectedItems | V[] | The selected items |
hasSelectedItems | boolean | Whether there's a selected item |
value | string[] | The selected item keys |
valueAsString | string | The string representation of the selected items |
selectValue | (value: string) => void | Function to select a value |
setValue | (value: string[]) => void | Function to set the value of the combobox |
clearValue | (value?: string) => void | Function to clear the value of the combobox |
focus | VoidFunction | Function to focus on the combobox input |
setInputValue | (value: string, reason?: InputValueChangeReason) => void | Function to set the input value of the combobox |
getItemState | (props: ItemProps) => ItemState | Returns the state of a combobox item |
setOpen | (open: boolean, reason?: OpenChangeReason) => void | Function to open or close the combobox |
collection | ListCollection<V> | Function to toggle the combobox |
reposition | (options?: Partial<PositioningOptions>) => void | Function to set the positioning options |
multiple | boolean | Whether the combobox allows multiple selections |
disabled | boolean | Whether the combobox is disabled |
getRootState | () => RootState | Returns the state of the root |
getContentState | () => ContentState | Returns the state of the content |
getTriggerState | (props?: TriggerProps) => TriggerState | Returns the state of the trigger |
Accessibility
Permalink to heading "Accessibility"| Key | Description |
|---|---|
ArrowDown | When the combobox is closed, opens the listbox and highlights to the first option. When the combobox is open, moves focus to the next option. |
ArrowUp | When the combobox is closed, opens the listbox and highlights to the last option. When the combobox is open, moves focus to the previous option. |
Home | When the combobox is open, moves focus to the first option. |
End | When the combobox is open, moves focus to the last option. |
Escape | Closes the listbox. |
Enter | Selects the highlighted option and closes the combobox. |
Esc | Closes the combobox |