PhoenixSelect (PhoenixSelect v1.0.0)

Copy Markdown View Source

An accessible select, multi-select, and searchable combobox for Phoenix LiveView.

The dropdown is rendered on the server; a single colocated hook (shipped inside this library, extracted at compile time) adds what markup alone cannot: keyboard navigation following the WAI-ARIA combobox pattern, client-side filtering, and focus management. There is nothing to npm install and no file to vendor.

Installation

Add the dependency, then register the hooks in assets/js/app.js:

import {hooks as phoenixSelectHooks} from "phoenix-colocated/phoenix_select"

const liveSocket = new LiveSocket("/live", Socket, {
  hooks: {...colocatedHooks, ...phoenixSelectHooks},
  ...
})

Requires LiveView ~> 1.1 and Phoenix 1.8+ (for colocated hook support). If you use Tailwind, add this library to your @source paths in app.css:

@source "../../deps/phoenix_select/lib";

Form mode (default)

Rendered inside your own <.form>; the selection participates in the form like any native input, driving your phx-change/phx-submit handlers:

<.form for={@form} phx-change="validate" phx-submit="save">
  <PhoenixSelect.select
    id="role"
    field={@form[:role]}
    options={[{"Admin", "admin"}, {"Editor", "editor"}]}
    label="Role"
  />

  <PhoenixSelect.select
    id="tags"
    field={@form[:tags]}
    options={["elixir", "phoenix", "liveview"]}
    multiple
    label="Tags"
  />
</.form>

Multi selects submit array params (user[tags][]). An empty selection submits [""] so that "clear all" reaches your handler — Ecto.Changeset.cast/4 filters the empty string out of array fields automatically.

URL params mode

Pass param, uri, and params instead of field/name and the selection is written to the URL query string via push_patch — the same state-lives-in-the-URL contract as Slab filters. Your LiveView only tracks uri and params in handle_params/3:

<PhoenixSelect.select
  id="genre-filter"
  param="filter[genre]"
  uri={@uri}
  params={@params}
  options={@genres}
  multiple
  reset_params={["page", "after"]}
/>

By default options are filtered client-side (zero latency, best for lists that fit comfortably in the DOM). For large or remote datasets, pass search_event and handle it in the parent LiveView — the hook pushes the event as the user types (debounced) instead of filtering locally:

<PhoenixSelect.select id="country" field={@form[:country]}
  options={@country_options} search_event="search_country" />
def handle_event("search_country", %{"id" => _id, "query" => query}, socket) do
  {:noreply, assign(socket, :country_options, Countries.search(query))}
end

Summary

Functions

Renders a searchable select input.

Functions

select(assigns)

Renders a searchable select input.

See the module documentation for usage examples of each mode.

Attributes

  • id (:string) (required) - unique DOM id; also used by the hook.
  • field (Phoenix.HTML.FormField) - form field, e.g. @form[:role] (form mode). Defaults to nil.
  • name (:string) - input name when not using field (form mode). Defaults to nil.
  • value (:any) - current value override; defaults to the field's value. Defaults to nil.
  • param (:string) - query param to write, e.g. "filter[genre]" (URL mode). Defaults to nil.
  • uri (:string) - current URI from handle_params/3 (URL mode). Defaults to nil.
  • params (:map) - current params from handle_params/3 (URL mode). Defaults to %{}.
  • reset_params (:list) - query params to delete whenever the selection changes, e.g. ["page"] (URL mode). Defaults to [].
  • options (:list) (required) - options as [{label, value}] tuples or plain values.
  • multiple (:boolean) - allow selecting more than one option. Defaults to false.
  • label (:string) - label text rendered above the input. Defaults to nil.
  • placeholder (:string) - placeholder for the search input. Defaults to nil.
  • disabled (:boolean) - disables the input. Defaults to false.
  • search_event (:string) - event pushed to the parent LiveView as the user types, instead of filtering client-side. Defaults to nil.
  • debounce (:integer) - debounce in ms for search_event pushes. Defaults to 300.
  • class (:string) - extra classes for the outer container. Defaults to nil.