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.

<.combobox
  id="combobox-single-only-values"
  name="breed"
  value="Golden Retriever"
  options={["Labrador Retriever", "German Shepherd", "Golden Retriever", "French Bulldog", "Bulldog"]}
  list_label="Dog breeds"
 />
<.combobox
  id="combobox-single-with-labels"
  name="breed"
  value="french_bulldog"
  options={[{"Labrador Retriever", "labrador"}, {"German Shepherd", "german_shepherd"}, {"Golden Retriever", "golden_retriever"}, {"French Bulldog", "french_bulldog"}, {"Bulldog", "bulldog"}]}
  list_label="Dog breeds"
 />
<.combobox
  id="combobox-single-with-blank-option"
  name="breed"
  value="golden_retriever"
  options={[{"No preference", ""}, {"Labrador Retriever", "labrador"}, {"Golden Retriever", "golden_retriever"}, {"Bulldog", "bulldog"}]}
  list_label="Dog breeds"
 />
<.combobox
  id="combobox-single-clearable"
  name="breed"
  value="golden_retriever"
  options={[{"Labrador Retriever", "labrador"}, {"German Shepherd", "german_shepherd"}, {"Golden Retriever", "golden_retriever"}, {"Bulldog", "bulldog"}]}
  clearable
  list_label="Dog breeds"
 />
<.combobox
  id="combobox-single-with-slots"
  name="breed"
  value="golden_retriever"
  options={[{"Labrador Retriever", "labrador"}, {"German Shepherd", "german_shepherd"}, {"Golden Retriever", "golden_retriever"}, {"Bulldog", "bulldog"}]}
  clearable
  list_label="Dog breeds"
>
  <: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><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>
</.combobox>
<.combobox
  id="combobox-single-with-labels-and-descriptions"
  name="breed"
  value="labrador"
  options={[[key: "Labrador Retriever", value: "labrador", description: "Friendly and outgoing"], [key: "German Shepherd", value: "german_shepherd", description: "Confident and smart"], [key: "French Bulldog", value: "french_bulldog", description: "Adaptable and playful", disabled: true]]}
  list_label="Dog breeds"
 />
<.combobox
  id="combobox-single-with-groups"
  name="breed"
  value="golden_retriever"
  options={[{"Retrievers", [{"Labrador Retriever", "labrador"}, {"Golden Retriever", "golden_retriever"}]}, :hr, {"Bulldogs", [{"French Bulldog", "french_bulldog"}, {"Bulldog", "bulldog"}]}, {"German Shepherd", "german_shepherd"}]}
  list_label="Dog breeds"
 />
<.combobox
  id="combobox-single-with-groups-and-descriptions"
  name="breed"
  value="golden_retriever"
  options={[{"Retrievers", [[key: "Labrador Retriever", value: "labrador", description: "Friendly and outgoing"], [key: "Golden Retriever", value: "golden_retriever", description: "Intelligent and friendly"]]}, :hr, {"Bulldogs", [[key: "French Bulldog", value: "french_bulldog", description: "Adaptable and playful"], [key: "Bulldog", value: "bulldog", description: "Docile and willful", disabled: true]]}, [key: "German Shepherd", value: "german_shepherd", description: "Confident and smart"]]}
  list_label="Dog breeds"
 />
<.combobox
  id="combobox-single-with-free-text"
  name="breed"
  options={[{"Labrador Retriever", "labrador"}, {"Golden Retriever", "golden_retriever"}, {"Bulldog", "bulldog"}]}
  free_text_label="Add breed"
  free_text
  list_label="Dog breeds"
 />
<.combobox
  disabled
  id="combobox-single-disabled"
  name="breed"
  value="golden_retriever"
  options={[{"Labrador Retriever", "labrador"}, {"Golden Retriever", "golden_retriever"}, {"Bulldog", "bulldog"}]}
  list_label="Dog breeds"
 />
<.combobox
  id="combobox-single-readonly"
  name="breed"
  value="golden_retriever"
  options={[{"Labrador Retriever", "labrador"}, {"Golden Retriever", "golden_retriever"}, {"Bulldog", "bulldog"}]}
  readonly
  list_label="Dog breeds"
 />
<form class="stack">
  <.combobox
    id="combobox-required-without-free-text"
    name="breed"
    options={[{"Labrador Retriever", "labrador"}, {"Golden Retriever", "golden_retriever"}, {"Bulldog", "bulldog"}]}
    required
    list_label="Dog breeds"
   />
  <.combobox
    id="combobox-required-with-free-text"
    name="other_breed"
    options={[{"Labrador Retriever", "labrador"}, {"Golden Retriever", "golden_retriever"}, {"Bulldog", "bulldog"}]}
    required
    free_text_label="Add breed"
    free_text
    list_label="Dog breeds"
   />
  <button type="submit" class="button">Submit</button>
</form>