Combobox
NewOverview
An editable input for filtering and selecting one or more options in a Phoenix form.
Combobox keeps DOM focus in a native
text input
while a listbox presents matching choices. It is intended for lists large enough to
benefit from filtering; use Select for a short list that does not need text input. In a
Phoenix form, pass
field={@form[:field_name]}
so values, names, validation errors, and initial selections come from the form.
<.form for={@form}>
<Combobox.combobox
field={@form[:assignee_id]}
label="Assignee"
sublabel="(optional)"
description="Choose the person responsible for this task."
placeholder="Search people…"
clearable
options={[
{"Ada Lovelace", "ada"},
{"Grace Hopper", "grace"},
{"Margaret Hamilton", "margaret"},
{"Katherine Johnson", "katherine"}
]}
/>
</.form>
Filtering
Options are filtered in the browser with a case-insensitive, diacritic-insensitive substring match against each label. All options remain in the DOM, so use Combobox for lists that are reasonable to render with the page.
<.form for={@form}>
<Combobox.combobox
field={@form[:programming_language]}
label="Programming language"
description="All 48 options are filtered in the browser."
placeholder="Search 48 languages…"
options={@language_options}
/>
</.form>
Multiple selection
Set multiple
to accept a list of values. Each selection is shown as a removable chip and submitted
through a repeated name[]
field. Selecting an active option toggles it without closing the popup.
From an empty search field, Backspace removes the last selection and Left Arrow moves to the chip removal controls. Their Left and Right Arrow keys move between selections; Backspace or Delete removes the focused selection.
<.form for={@form}>
<Combobox.combobox
field={@form[:reviewer_ids]}
label="Reviewers"
description="Choose one or more people to review this change."
placeholder="Search people…"
multiple
clearable
options={[
{"Ada Lovelace", "ada"},
{"Grace Hopper", "grace"},
{"Margaret Hamilton", "margaret"},
{"Katherine Johnson", "katherine"}
]}
/>
</.form>
Creatable tags
Combine multiple
and allow_custom_value
for tag entry. Enter
commits each typed value as a removable chip while the supplied options remain useful
suggestions.
<.form for={@form}>
<Combobox.combobox
field={@form[:skills]}
label="Skills"
description="Choose suggestions or enter a new skill."
placeholder="Add a skill…"
multiple
allow_custom_value
clearable
options={["Elixir", "Phoenix", "LiveView", "PostgreSQL", "Tailwind CSS"]}
/>
</.form>
Automatic highlighting
auto_highlight visually activates the
first match when the popup opens or the results change. Enter accepts that match;
assistive-technology focus remains on the input until keyboard navigation explicitly
moves through the list.
<.form for={@form}>
<Combobox.combobox
field={@form[:airport]}
label="Arrival airport"
placeholder="Search airports…"
auto_highlight
options={[
{"Detroit Metropolitan Airport (DTW)", "DTW"},
{"Chicago O'Hare International Airport (ORD)", "ORD"},
{"John F. Kennedy International Airport (JFK)", "JFK"},
{"Los Angeles International Airport (LAX)", "LAX"}
]}
/>
</.form>
Preselected and clearable
Initial selections come from the form field: use a scalar field value for single
selection or a list for multiple selection.
clearable
adds a named clear control that
resets the submitted form value.
<.form for={@form}>
<Combobox.combobox
field={@form[:country]}
label="Country"
clearable
options={[
{"Canada", "CA"},
{"Mexico", "MX"},
{"United Kingdom", "GB"},
{"United States", "US"}
]}
/>
</.form>
Groups and custom options
Group tuples add labelled option groups. The
option
slot can add text and decorative graphics, but must not contain buttons, links, or
other interactive descendants because focus remains in the combobox input.
<.form for={@form}>
<Combobox.combobox
field={@form[:time_zone]}
label="Time zone"
placeholder="Search time zones…"
options={[
{"Americas", [
{"Detroit", "America/Detroit"},
{"Denver", "America/Denver"},
{"Los Angeles", "America/Los_Angeles"}
]},
{"Europe", [
{"London", "Europe/London"},
{"Paris", "Europe/Paris"}
]}
]}
>
<:option :let={option}>
<span class="grid min-w-0">
<span class="truncate">{option.label}</span>
<span class="truncate text-xs text-muted-foreground">{option.value}</span>
</span>
</:option>
</Combobox.combobox>
</.form>
Option formats and disabled options
Options accept strings, label-value tuples, keyword lists, and maps. Map and keyword
options can set disabled: true; disabled
results remain visible but cannot be selected and are skipped by arrow navigation.
<.form for={@form}>
<Combobox.combobox
field={@form[:runtime]}
label="Runtime"
description="The unavailable runtime stays visible but cannot be selected."
options={[
"BEAM",
{"Node.js", "node"},
[label: "Bun", value: "bun"],
%{label: "Deno", value: "deno"},
%{label: "Workerd (unavailable)", value: "workerd", disabled: true}
]}
/>
</.form>
Popup placement and height
Combobox accepts the shared Floating UI placement options. This example requests a
top-start popup with automatic placement fallback, an eight-pixel offset, and a shorter
scrolling result panel through popup_class.
<.form for={@form}>
<Combobox.combobox
field={@form[:release]}
label="Release"
placeholder="Search releases…"
side={:top_start}
placement_strategy={:auto_placement}
offset={8}
popup_class="max-h-36"
options={Enum.map(1..20, &{"Release 2026.#{&1}", "2026.#{&1}"})}
/>
</.form>
Empty and loading states
Customize empty and loading announcements for the domain. The loading example sets
loading
while its option source is being prepared. Loading marks both the input and listbox as
busy for assistive technology.
<.form for={@form} class="grid w-full gap-6 md:grid-cols-2">
<Combobox.combobox
field={@form[:project_id]}
label="Archived project"
placeholder="Search archived projects…"
empty_message="No archived projects match this search."
options={[]}
/>
<Combobox.combobox
field={@form[:loading_project_id]}
label="Imported project"
placeholder="Search imported projects…"
loading
loading_message="Loading imported projects…"
options={[]}
/>
</.form>
Custom values
Enable allow_custom_value when the list
contains suggestions rather than every valid value. Enter or leaving the field commits
unmatched text; Escape restores the previous committed value.
<.form for={@form}>
<Combobox.combobox
field={@form[:label]}
label="Label"
description="Choose a suggestion or enter a new label."
placeholder="Search or create a label…"
allow_custom_value
clearable
options={["Bug", "Feature", "Documentation", "Question"]}
/>
</.form>
Errors
When bound with field, validation errors
are associated with the input through aria-describedby.
Required restricted comboboxes also reject unmatched text during native form validation.
<.form for={@form}>
<Combobox.combobox
field={@form[:assignee_id]}
label="Assignee"
placeholder="Search people…"
required
options={[
{"Ada Lovelace", "ada"},
{"Grace Hopper", "grace"}
]}
/>
</.form>
Form submission
The examples use to_form/2
with as: :combobox_example.
A single-selection field submits one string value. The visible label and current search
query are not submitted, so choosing “Grace Hopper” from the Assignee example produces:
%{
"combobox_example" => %{
"assignee_id" => "grace"
}
}
A multiple-selection field uses repeated name[]
inputs. Its raw params start with an empty sentinel so the field is still submitted after
every selection is removed. With Ada Lovelace and Grace Hopper selected, the Reviewers
example produces:
%{
"combobox_example" => %{
"reviewer_ids" => ["", "ada", "grace"]
}
}
With no reviewers selected, reviewer_ids
is [""]. Remove the empty sentinel before
passing raw values to domain logic. Custom values use the same string representation as
supplied options. Changing the form’s :as
option changes the outer params key; a disabled combobox is omitted from submission.
Keyboard commands
DOM focus remains on the text input while an option is active. Commands for selected-value
removal buttons apply only when multiple
is enabled. Other text entry and editing commands retain native input behavior.
| Focus | Key | Behavior |
|---|---|---|
| Text input |
Typing
|
Opens the popup and filters option labels with a case-insensitive, diacritic-insensitive substring match. Input from an IME is filtered after composition finishes. |
| Text input |
Down Arrow
Up Arrow
|
Opens the popup when needed and moves the active option without moving DOM focus from the input. Disabled options are skipped and navigation stops at the first or last result. |
| Text input |
Alt + Down Arrow
|
Opens the popup without making an option active. |
| Text input |
Alt + Up Arrow
|
Closes the popup without changing the current selection. |
| Text input |
Enter
|
When the popup is open, selects the active option, an exact label match, or an allowed custom value. Multiple selection keeps the popup open. With the popup closed, Enter retains the browser's native form behavior. |
| Text input |
Escape
|
When the popup is open, discards the current query, restores the committed single-selection label or clears the multiple-selection query, and closes the popup. Existing selections are unchanged. |
| Text input |
Tab
|
Commits an exact match or allowed custom value, restores restricted unmatched text, closes the popup, and continues native focus navigation. |
| Text input |
Left Arrow
Right Arrow
Home
End
|
Retains native text-cursor behavior and clears the active option. In multiple mode, Left Arrow at the start of an empty input moves focus to the last selected-value removal button. |
| Text input, multiple selection |
Backspace
|
From an empty input, removes the last selected value. |
| Selected-value removal button |
Left Arrow
Right Arrow
|
Moves between removal buttons. Moving before the first or after the last returns focus to the text input. |
| Selected-value removal button |
Backspace
Delete
|
Removes the selected value and moves focus to the previous removal button, or to the text input when none precedes it. |
| Selected-value removal button |
Enter
Space
|
Uses native button activation to remove the selected value and returns focus to the text input. |
Accessibility
The component follows the WAI-ARIA combobox pattern.
The input owns role="combobox", aria-expanded, and aria-controls. DOM focus remains in the input
while aria-activedescendant identifies the
option reached with Up or Down Arrow. Enter commits it, Escape closes the list without
changing the committed value, and Alt with Down or Up Arrow opens or closes the popup.
Typing and horizontal cursor movement clear the active descendant so assistive technology
can announce native text editing. Input filtering waits for IME composition to finish.
Home, End, and other editing shortcuts remain native input behavior.
The ARIA Authoring Practices combobox pattern describes a single-select widget. In
multiple mode, Dizzy combines its editable-input focus model with the multiselectable listbox pattern:
the popup has aria-multiselectable="true",
every selectable option exposes its selection state, and selected values appear in a
labelled toolbar with individually named removal buttons. Polite status updates report
result-count changes, while selection and removal updates use a separate assertive live
region.
Options may contain text and decorative graphics but not nested interactive controls.
Disabled options remain perceivable but are skipped by keyboard navigation. The default
manual-selection behavior does not highlight the first filtered result until the user
presses an arrow key; enable auto_highlight
only when automatic suggestion selection is intentional.
Add this module
mix dizzy.add combobox
Component API
Combobox.combobox
Attributes
| Name | Type | Default | Values |
|---|---|---|---|
allow_custom_value |
:boolean
| false | — |
auto_highlight |
:boolean
| false | — |
class |
:any
| nil | — |
clearable |
:boolean
| false | — |
description |
:string
| nil | — |
disabled |
:boolean
| false | — |
empty_message |
:string
| "No options found." | — |
errors |
:list
| [] | — |
field |
{:struct, Phoenix.HTML.FormField}
| nil | — |
hide |
:boolean
| false | — |
id |
:string
| nil | — |
label |
:string
| nil | — |
loading |
:boolean
| false | — |
loading_message |
:string
| "Loading options…" | — |
multiple |
:boolean
| false | — |
multiple_instructions |
:string
| "Multiple selections allowed. Press Backspace in an empty search field to remove the last selection. Selected options also have remove buttons." | — |
name |
:string
| nil | — |
offset |
:integer
| 4 | — |
options |
:list
| Required | — |
placeholder |
:string
| "Search options…" | — |
placement_strategy |
:atom
| :flip | nil, :flip, :auto_placement |
popup_class |
:any
| nil | — |
required |
:boolean
| false | — |
rest |
:global
| nil | — |
selection_required_message |
:string
| "Choose an option from the list." | — |
shift |
:boolean
| true | — |
side |
:atom
| :bottom_start | :top, :top_start, :top_end, :bottom, :bottom_start, :bottom_end |
strategy |
:atom
| :fixed | :absolute, :fixed |
sublabel |
:string
| nil | — |
value |
:any
| nil | — |
Slots
| Name | Required |
|---|---|
option | Optional |
Combobox.chip
Attributes
| Name | Type | Default | Values |
|---|---|---|---|
item |
:map
| Required | — |
Combobox.options
Attributes
| Name | Type | Default | Values |
|---|---|---|---|
option_slot |
:list
| Required | — |
options |
:list
| Required | — |
Combobox.option
Attributes
| Name | Type | Default | Values |
|---|---|---|---|
option |
:map
| Required | — |
option_slot |
:list
| Required | — |
JavaScript hook
DizzyCombobox
Keeps focus in the native text input while filtering options, handling IME composition, moving the active descendant, announcing result and selection changes, committing selections, restoring cancelled edits, and synchronizing Phoenix form values. Multiple selections render as keyboard-removable chips. It also positions the listbox with Floating UI.
mix dizzy.add includes the hook and performs
any required asset setup automatically.