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"]}
/>Server-side search
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
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 tonil.name(:string) - input name when not usingfield(form mode). Defaults tonil.value(:any) - current value override; defaults to the field's value. Defaults tonil.param(:string) - query param to write, e.g."filter[genre]"(URL mode). Defaults tonil.uri(:string) - current URI fromhandle_params/3(URL mode). Defaults tonil.params(:map) - current params fromhandle_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 tofalse.label(:string) - label text rendered above the input. Defaults tonil.placeholder(:string) - placeholder for the search input. Defaults tonil.disabled(:boolean) - disables the input. Defaults tofalse.search_event(:string) - event pushed to the parent LiveView as the user types, instead of filtering client-side. Defaults tonil.debounce(:integer) - debounce in ms forsearch_eventpushes. Defaults to300.class(:string) - extra classes for the outer container. Defaults tonil.