Combobox

Renders a text input with a popup that allows users to select a value from a list of suggestions.

Read more Read less

Usage

Options

With simple values:

<.combobox
  id="dog-breed-selector"
  name="breed"
  list_label="Dog breeds"
  options={[
    "Labrador Retriever",
    "German Shepherd",
    "Golden Retriever",
    "French Bulldog",
    "Bulldog"
  ]}
/>

With label/value pairs:

<.combobox
  id="dog-breed-selector"
  name="breed"
  list_label="Dog breeds"
  options={[
    {"Labrador Retriever", "labrador"},
    {"German Shepherd", "german_shepherd"},
    {"Golden Retriever", "golden_retriever"},
    {"French Bulldog", "french_bulldog"},
    {"Bulldog", "bulldog"}
  ]}
/>

With descriptions and a disabled option:

<.combobox
  id="dog-breed-selector"
  name="breed"
  list_label="Dog breeds"
  options={[
    [key: "Labrador Retriever", value: "labrador", description: "Friendly and outgoing"],
    [key: "German Shepherd", value: "german_shepherd", description: "Confident and smart"],
    [key: "Bulldog", value: "bulldog", description: "Docile and willful", disabled: true]
  ]}
/>

Label

The component does not render a label by itself, so you must render one yourself. This is important for accessibility: without it the text input has no accessible name, and a screen reader announces it as a combobox without saying what is being chosen.

The usual way is a <label> whose for attribute is the id you passed:

<label for="dog-breed-selector">Breed</label>
<.combobox
  id="dog-breed-selector"
  name="breed"
  list_label="Dog breeds"
  options={@breeds}
/>

If the name is already on the page, for example in the form of a heading, you can set aria-labelledby to the ID of that element instead:

<h2 id="breed-heading">Breed</h2>
<.combobox
  id="dog-breed-selector"
  name="breed"
  list_label="Dog breeds"
  aria-labelledby="breed-heading"
  options={@breeds}
/>

aria-labelledby and aria-label are global attributes that are set on the text input, which is the element that needs the label.

By contrast, list_label labels the listbox and the button that opens it. It describes the list of options, not the field.

In a form

To use the component in a form, you need to pass the id, name, and value, and add the label, description, and errors.

<.form for={@form} phx-change="validate" phx-submit="save">
  <label for="dog-breed-selector">Breed</label>
  <.combobox
    id="dog-breed-selector"
    name={@form[:breed].name}
    value={@form[:breed].value}
    list_label="Dog breeds"
    options={@breeds}
  />
</.form>

The component renders a hidden input with the given name. Its value is the selected option value, or, if a free text entry is selected, the entered value.

The text input the user types uses the given name with a _search suffix. The submitted value is either the label of the selected option or the current search term. You can use this value to filter options on the server side, or otherwise ignore it.

With a clear button

If the clearable attribute is set, a clear button is rendered that unselects the current selection and clears the search term.

<.combobox
  id="dog-breed-selector"
  name="breed"
  list_label="Dog breeds"
  clearable
  clear_label="Clear breed"
  options={@breeds}
/>

Both the toggle button and the clear button have default content that can be overridden with the :toggle and :clear slots.

<.combobox id="dog-breed-selector" name="breed" list_label="Dog breeds" clearable options={@breeds}>
  <:clear><Heroicon.x_mark /></:clear>
  <:toggle><Heroicon.chevron_down /></:toggle>
</.combobox>

With free text

If the free_text attribute is set, the user can choose to submit an entered value that is not among the options.

<.combobox
  id="dog-breed-selector"
  name="breed"
  list_label="Dog breeds"
  free_text
  free_text_label="Add breed"
  options={@breeds}
/>

With options loaded from the server

Set on_search to filter on the server. If set, the hook stops filtering on the client side, and your handler receives the typed text as the *_search parameter described above.

<.combobox
  id="dog-breed-selector"
  name="breed"
  list_label="Dog breeds"
  options={@breeds}
  on_search="search-breeds"
/>
def handle_event("search-breeds", %{"breed_search" => term}, socket) do
  {:noreply, assign(socket, breeds: Dogs.search_breeds(term))}
end

Instead of an event name, you can also pass a Phoenix.LiveView.JS command.

Keyboard

  • Down - open the listbox, or move to the next option. Opening moves to the selected option, or to the first one if nothing is selected.
  • Up - open the listbox at the last option, or move to the previous one.
  • Alt + Down - open the listbox without moving to an option.
  • Alt + Up - close the listbox, leaving the text as it is.
  • Enter - select the active option.
  • Escape - close the listbox and put the display value of the selection back in the input. With the listbox already closed and the display value unchanged, clear the selection.

The combobox is a single tab stop. Focus stays on the text input and never moves into the listbox. The active option is tracked with aria-activedescendant. The toggle and the clear button are out of the tab order; keyboard users can use Alt + Down and Alt + Up for the toggle, and Escape for clearing.

Home, End, Left, Right, Backspace and Delete are not intercepted and are reserved for the browser's text editing.

Attribute Type Documentation Default Value
class :any

Any additional classes to be added.

Read more Read less
[]

Variations of the component should be expressed via modifier attributes, and it is preferable to use styles on the parent container to arrange components on the page, but if you have to, you can use this attribute to pass additional utility classes to the component.

The value can be a string or a list of strings.

clear_label :string

Aria label for the clear button. This value should be translated to the

Read more Read less
"Clear"

language in which the rest of the page is displayed.

clearable :boolean

If true, a clear button is rendered.

Read more Read less
false

The button is hidden while there is nothing to clear. Pressing Escape on a closed listbox clears the selection whether or not the button is rendered.

display_value :string

The input value for the current value.

Read more Read less

Defaults to the label of the option matching value and falls back to the value if no option matches.

Set this attribute if the value may not be among the options, for example if the options are loaded dynamically from the server based on the search term, as opposed to passing a fixed set of options.

free_text :boolean

If true, users can submit a free text value that is not among the

Read more Read less
false

options.

Requires free_text_label.

free_text_label :string

A label for the option to submit the typed text, for example

Read more Read less

"Add breed". Required when free_text is set.

Required id * :string

Sets the DOM ID for the input.

Required list_label * :string

Sets the aria label for the list box. For example, if the combobox allows

Read more Read less

the user to select a country, the list label could be "Countries". The value should start with an uppercase letter and be localized.

Required name * :string

Sets the name of the hidden input that submits the value. The name of

Read more Read less

the text input is the same name with the _search suffix.

on_search :any

An event name as a string or a Phoenix.LiveView.JS command to emit

Read more Read less

when the user types. Use this for filtering options on the server side.

If set, the component adds a phx-change attribute to the text input and the hook stops filtering. The search is debounced by 300 ms. You can override the default by passing the phx-debounce attribute.

If not set, the hook filters the passed options on the client side, and the search is not debounced by default.

To use this attribute, the input must be inside a form, or else LiveView raises.

Required options * :list

A list of available options.

Read more Read less

The format is the same as the one accepted by the select, "radio-group", and "checkbox-group" types of the field component. See also Phoenix.HTML.Form.options_for_select/2.

  • A primitive value is used as both label and value.
  • In a 2-tuple, the first element is the label and the second is the input value.
  • A map results in one option per key/value pair.
  • :hr renders a separator between options.

You can also group options:

options={[{"Retrievers", [{"Golden Retriever", "golden"}]}]}

An option can also be written as a keyword list with these keys:

  • :key (required) - the label
  • :value (required) - the input value
  • :description (optional) - rendered under the label
  • :disabled (optional) - renders aria-disabled, and the option is skipped by the arrow keys and cannot be selected

Example:

options={[[key: "Golden Retriever", value: "golden", description: "Friendly"]]}
rest :global

Any additional HTML attributes. These are set on the text input, not on

Read more Read less
%{autocomplete: "off"}

the wrapper element.

disabled and form are set on the hidden input as well.

value :string

The current input value. The display value for the text input is derived

Read more Read less

by finding the given value in the list of options.

clear :slot

The content for the clear button. Defaults to a multiplication sign.

The accessible name comes from clear_label either way.

<:clear><svg
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
  class="lucide lucide-x"
>
  <path d="M18 6 6 18" />
  <path d="m6 6 12 12" />
</svg>
</:clear>
toggle :slot

The content for the button that opens the listbox. Defaults to a downwards-pointing triangle.

The accessible name comes from list_label either way.

<:toggle><svg
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
  class="lucide lucide-chevron-down"
>
  <path d="m6 9 6 6 6-6" />
</svg>
</:toggle>