Combobox

New

Overview

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.

Choose the person responsible for this task.

HEEx
<.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.

All 48 options are filtered in the browser.

HEEx
<.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.

Choose one or more people to review this change.

Multiple selections allowed. Press Backspace in an empty search field to remove the last selection. Selected options also have remove buttons.

HEEx
<.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.

Choose suggestions or enter a new skill.

Multiple selections allowed. Press Backspace in an empty search field to remove the last selection. Selected options also have remove buttons.

HEEx
<.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.

HEEx
<.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.

HEEx
<.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.

HEEx
<.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.

The unavailable runtime stays visible but cannot be selected.

HEEx
<.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.

HEEx
<.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.

HEEx
<.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.

Choose a suggestion or enter a new label.

HEEx
<.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.

can't be blank

HEEx
<.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:

LiveView params
%{
  "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:

LiveView params
%{
  "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.

Combobox keyboard commands
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.

Add this module

terminal
mix dizzy.add combobox

Component API

Combobox.combobox

Attributes

Attributes for Combobox.combobox
NameTypeDefault 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 :flipnil, :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

Slots for Combobox.combobox
NameRequired
option Optional

Combobox.chip

Attributes

Attributes for Combobox.chip
NameTypeDefault Values
item :map Required—

Combobox.options

Attributes

Attributes for Combobox.options
NameTypeDefault Values
option_slot :list Required—
options :list Required—

Combobox.option

Attributes

Attributes for Combobox.option
NameTypeDefault 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.

Explore Dizzy