PhiaUi.Components.Combobox (phia_ui v0.1.17)

Copy Markdown View Source

Combobox component with search/filter for PhiaUI.

A searchable selection widget that renders as a trigger button + dropdown panel. Filtering is server-side: the parent LiveView handles the search event and passes back a filtered options list. No client-side JS hook is required for basic functionality.

Sub-components

When to use

Use combobox/1 when a <select> element would have too many options and users benefit from typing to filter — country pickers, user selectors, product/SKU selectors, time-zone pickers, etc.

For simple short lists (< 10 items), use a native filter_select/1 instead.

Three-event model

The parent LiveView must handle exactly three events:

EventWhen firedPayload
on_toggleTrigger button clickednone (params ignored)
on_searchUser types in the search input%{"query" => string}
on_changeUser selects an option%{"value" => string}

Complete example — country selector

defmodule MyAppWeb.ProfileLive do
  use Phoenix.LiveView

  @countries [
    %{value: "us", label: "United States"},
    %{value: "gb", label: "United Kingdom"},
    %{value: "de", label: "Germany"},
    %{value: "fr", label: "France"},
    # ... etc.
  ]

  def mount(_params, _session, socket) do
    {:ok, assign(socket,
      country: nil,
      country_open: false,
      country_search: ""
    )}
  end

  def handle_event("toggle-country", _params, socket) do
    {:noreply, update(socket, :country_open, &(!&1))}
  end

  def handle_event("search-country", %{"query" => q}, socket) do
    {:noreply, assign(socket, country_search: q)}
  end

  def handle_event("pick-country", %{"value" => v}, socket) do
    {:noreply, assign(socket,
      country: v,
      country_open: false,
      country_search: ""
    )}
  end

  # Filter the options server-side based on search query
  defp filtered_countries(search) do
    query = String.downcase(search)
    Enum.filter(@countries, &String.contains?(String.downcase(&1.label), query))
  end
end

<%!-- Template --%>
<.combobox
  id="country-picker"
  options={filtered_countries(@country_search)}
  value={@country}
  open={@country_open}
  search={@country_search}
  placeholder="Select a country..."
  on_toggle="toggle-country"
  on_search="search-country"
  on_change="pick-country"
/>

Form-integrated example

<.form for={@form} phx-submit="save">
  <.form_combobox
    id="timezone-picker"
    field={@form[:timezone]}
    options={filtered_timezones(@timezone_search)}
    value={@selected_timezone}
    open={@timezone_open}
    search={@timezone_search}
    placeholder="Select timezone..."
    on_toggle="toggle-tz"
    on_search="search-tz"
    on_change="pick-tz"
  />
  <.button type="submit">Save</.button>
</.form>

Options format

Options can be passed in two formats:

# Map format (preferred)
options={[%{value: "us", label: "United States"}, ...]}

# Tuple format (compatible with Phoenix.HTML.Form select helpers)
options={[{"United States", "us"}, {"Germany", "de"}, ...]}

Both formats are normalised internally to %{value: string, label: string}.

ARIA

The trigger button has aria-haspopup="listbox" and aria-expanded toggled with the open assign. The dropdown has role="listbox" and each option has role="option" with aria-selected. Keyboard navigation (Escape / Enter / arrow keys) requires the optional PhiaCombobox JS hook.

Summary

Functions

Renders a combobox with a trigger button and searchable dropdown.

Renders a combobox integrated with Phoenix.HTML.FormField.

Functions

combobox(assigns)

Renders a combobox with a trigger button and searchable dropdown.

The component is fully server-driven: open, search, value, and options are all controlled by the LiveView. On each re-render, the options list is normalised and filtered by the current search query for cases where all options are passed statically (no server-side filtering).

For large option lists (hundreds of items), handle on_search in the LiveView and pass a pre-filtered options list instead of relying on client-side filtering.

Attributes

  • id (:string) (required) - Unique combobox DOM id.

  • value (:string) - Currently selected value string, or nil when nothing is selected. Defaults to nil.

  • placeholder (:string) - Trigger button text shown when no value is selected. Defaults to "Select an option...".

  • search_placeholder (:string) - Placeholder text for the search input inside the dropdown. Defaults to "Search...".

  • options (:list) - List of options in either format:

    • %{value: string, label: string} maps
    • {label, value} 2-tuples (compatible with Phoenix.HTML.Form)

    Pass a pre-filtered subset when the LiveView handles on_search.

    Defaults to [].

  • open (:boolean) - Whether the dropdown panel is currently visible. Controlled by the LiveView. Defaults to false.

  • search (:string) - Current search query — used for client-side label filtering when options is static. Defaults to "".

  • on_change (:string) - phx-click event name emitted when a user selects an option. The LiveView receives %{"value" => value}.

    Defaults to "combobox-change".

  • on_search (:string) - phx-change event name emitted by the search input. The LiveView receives %{"query" => query}. Use this to filter options server-side and re-assign.

    Defaults to "combobox-search".

  • on_toggle (:string) - phx-click event name emitted when the trigger button is clicked. The LiveView should toggle the open assign: update(socket, :open, &(!&1)).

    Defaults to "combobox-toggle".

  • class (:string) - Additional CSS classes merged via cn/1. Defaults to nil.

  • Global attributes are accepted. HTML attributes forwarded to the root div.

form_combobox(assigns)

Renders a combobox integrated with Phoenix.HTML.FormField.

Injects a <input type="hidden"> bound to field.name so the selected value is included in phx-submit form payloads. Changeset errors from field.errors are displayed as destructive text below the widget.

Example

<.form_combobox
  id="assignee-picker"
  field={@form[:assignee_id]}
  options={@team_members}
  value={@selected_assignee}
  open={@assignee_open}
  search={@assignee_search}
  placeholder="Assign to..."
  on_toggle="toggle-assignee"
  on_search="search-assignee"
  on_change="pick-assignee"
/>

Attributes

  • field (Phoenix.HTML.FormField) (required) - Phoenix.HTML.FormField struct from @form[:field_name]. Provides id, name, and errors for form integration.

  • id (:string) (required) - Unique combobox DOM id.

  • value (:any) - Currently selected value (string or nil). Defaults to nil.

  • placeholder (:string) - Trigger button placeholder text. Defaults to "Select an option...".

  • search_placeholder (:string) - Placeholder text for the search input. Defaults to "Search...".

  • options (:list) - Options list (same format as combobox/1). Defaults to [].

  • open (:boolean) - Whether the dropdown is visible. Defaults to false.

  • search (:string) - Current search query. Defaults to "".

  • on_change (:string) - phx-click event for option selection. Defaults to "combobox-change".

  • on_search (:string) - phx-change event for search input. Defaults to "combobox-search".

  • on_toggle (:string) - phx-click event for the trigger button. Defaults to "combobox-toggle".

  • class (:string) - Additional CSS classes. Defaults to nil.