Forms & inputs
Select & autocomplete
Client and server-driven option pickers.
Select
FormField-aware select (Fluxon API): rich listbox with keyboard nav +
type-ahead over a hidden input, or a native fallback.
Basic
Rich listbox with label, options, and placeholder.
<.select
name="channel"
label="Channel"
options={[{"eBay", "ebay"}, {"Shopify", "shopify"}, {"Direct", "direct"}]}
placeholder="Pick a channel"
/>
With value
Controlled value selects the matching option.
<.select
name="status"
label="Status"
value="active"
options={[{"Active", "active"}, {"Archived", "archived"}]}
/>
Multiple
multiple keeps the panel open and submits name[] for each selection.
<.select
name="tags"
label="Multiple"
multiple
value={["elixir", "phoenix"]}
options={[
{"Elixir", "elixir"},
{"Phoenix", "phoenix"},
{"LiveView", "liveview"},
{"Ecto", "ecto"}
]}
/>
Searchable
searchable adds a filter box in the listbox (or set search_threshold).
No results
<.select
name="country"
label="Searchable"
searchable
placeholder="Pick a country"
options={["Argentina", "Australia", "Brazil", "Canada", "Denmark"]}
/>
Multi + search
Combine multiple and searchable for large option sets.
No results
<.select
name="team"
label="Multi + search"
multiple
searchable
options={["Ada", "Alan", "Barbara", "Donald", "Edsger", "Grace", "Ken", "Radia"]}
/>
Native
native renders a plain <select> — useful for dense tool UIs.
<.select name="size" label="Native" native value={25} options={[10, 25, 50]} />
Error state
Same FormField / errors list pattern as input.
can't be blank
<.select name="bad" label="With error" options={["a"]} errors={["can't be blank"]} />
Client mode (default)
Zag owns the value from data-default-value; picks sync the hidden input and fire input/change, so an existing phx-change keeps working with no server round trip.
<.select
id="sel-client"
name="channel"
label="Client mode"
value="shopify"
options={[{"eBay", "ebay"}, {"Shopify", "shopify"}, {"Direct", "direct"}]}
/>
Server-driven (controlled)
controlled makes the server value truth: client picks flow out through on_change, and server patches flow back into the machine. Pick in the listbox, or drive it from the server buttons.
Server value: active
<.select
id="sel-controlled"
name="status"
label="Controlled"
controlled
on_change="controlled_status_changed"
value={@controlled_status}
options={[{"Active", "active"}, {"Archived", "archived"}]}
/>
API reference
Props and slots, introspected from the component.
| Prop | Type | Default | Description |
|---|---|---|---|
class |
any |
nil |
Extra classes merged onto the root element. |
clearable |
boolean |
false |
show a clear (×) button that resets the selection (Fluxon parity). |
controlled |
boolean |
false |
server-driven mode: the server `value` is truth and patches flow into the Zag machine (non-searchable rich path only). |
description |
string |
nil |
Helper text under the label stack. |
disabled |
boolean |
false |
Render disabled and non-interactive. |
errors |
list |
[] |
Validation messages; derived from field when used. |
field |
{:struct, Phoenix.HTML.FormField} |
nil |
Form field; derives id, name, value, and errors. |
help_text |
string |
nil |
Trailing help line under the field when no errors. |
id |
any |
nil |
Element id. Derived from `field` when omitted. With a bare `name` and no `field`, the rich path falls back to `name` (the id feeds a hook, so it must stay stable) while `native` auto-generates a unique `lui-select-<n>`, mirroring Fluxon. Pass an explicit id to get a stable, predictable id on the native path. |
include_hidden |
boolean |
true |
Emit a blank hidden input so empty submits. |
label |
string |
nil |
Primary label above the control. |
max |
integer |
nil |
max selections when multiple |
multiple |
boolean |
false |
multi-select; submits name[] hidden inputs |
name |
any |
nil |
Form input name; derived from field when omitted. |
native |
boolean |
false |
Use a native <select> instead of the rich listbox. |
on_change |
string |
nil |
server event pushed with `%{id, value}` when the Zag select value changes (non-searchable rich path only). |
on_change_client |
string |
nil |
bubbling DOM CustomEvent dispatched with `%{id, value}` on Zag value changes (non-searchable rich path only). |
options |
list |
[] |
Choices as values or {label, value} tuples. |
placeholder |
string |
"Select…" |
Toggle text when nothing is selected. |
prompt |
string |
nil |
blank first option (native path) |
search_input_placeholder |
string |
"Search…" |
Placeholder for the listbox search input. |
search_no_results_text |
string |
"No results" |
Empty-state copy when search matches nothing. |
search_threshold |
integer |
nil |
auto-enable search at N+ options |
searchable |
boolean |
false |
search box inside the listbox |
size |
xs | sm | md | lg | xl |
"md" |
Control density / type scale. |
sublabel |
string |
nil |
Secondary label line under the primary label. |
value |
any |
nil |
Selected value(s); list when multiple. |
Autocomplete
Accessible static or LiveView-backed search. Lantern owns the combobox, keyboard selection, and presentation; your LiveView owns remote querying, authorization, and result order. The public API mirrors Fluxon 2.3.1.
Static filtering
Static options filter in the browser; selection fills the hidden form input.
<.autocomplete
id="ac-fruit"
name="fruit"
label="Fruit"
placeholder="Search fruit…"
options={["Apple", "Apricot", "Banana", "Blackberry", "Cherry", "Grape"]}
/>
Server-backed search
Type at least two characters (try “zel”). The LiveView filters server-owned data and patches grouped, rich results back into the same focused combobox.
Server-backed, grouped results with rich option rows.
# LiveView
def handle_event("search_catalog", %{"query" => query}, socket) do
{:noreply, assign(socket, :catalog_options, search_catalog(query))}
end
<.autocomplete
id="ac-catalog"
name="game_id"
label="Game catalog"
options={@catalog_options}
on_search="search_catalog"
search_threshold={2}
debounce={250}
clearable
no_results_text="No games match %{query}"
>
<:option :let={{label, value}}>
<span>{label}</span><code>{value}</code>
</:option>
</.autocomplete>
API reference
Props and slots, introspected from the component.
| Prop | Type | Default | Description |
|---|---|---|---|
animation |
string |
"transition duration-150 ease-in-out" |
Accepted for Fluxon compatibility; currently a CSS/token-driven no-op. |
animation_enter |
string |
"opacity-100 scale-100" |
Accepted for Fluxon compatibility; currently a no-op. |
animation_leave |
string |
"opacity-0 scale-95" |
Accepted for Fluxon compatibility; currently a no-op. |
autofocus |
boolean |
false |
Focus the search input on page load. |
class |
any |
nil |
Extra classes merged onto the field wrapper. |
clearable |
boolean |
false |
Show a button that clears the selection. |
debounce |
integer |
200 |
Server-search debounce in milliseconds. |
description |
string |
nil |
Description between label and control. |
disabled |
boolean |
false |
Disable searching and selection. |
errors |
list |
[] |
Validation messages; derived from field when used. |
field |
{:struct, Phoenix.HTML.FormField} |
nil |
Form field; derives id, name, value, and errors. |
help_text |
string |
nil |
Help line shown when there are no errors. |
id |
any |
nil |
Element id; derived from field when omitted. |
label |
string |
nil |
Primary label above the control. |
name |
any |
nil |
Form input name; derived from field when omitted. |
no_results_text |
string |
"No results" |
Empty copy; an optional `%{query}` placeholder is replaced in the browser. |
on_search |
string |
nil |
LiveView search event receiving `%{"query" => query}`. |
open_on_focus |
boolean |
false |
Open the suggestions when focused. |
options |
list |
[] |
Choices as values, tuples, tuple-child groups, or `{:group, label, children}`. |
placeholder |
string |
"Search…" |
Placeholder shown in the search input. |
search_mode |
contains | starts-with | exact |
"contains" |
Static matching mode. |
search_threshold |
integer |
0 |
Characters required before searching. |
size |
xs | sm | md | lg | xl |
"md" |
Control density and type scale. |
sublabel |
string |
nil |
Secondary label beside the primary label. |
value |
any |
nil |
Current selected value. |
| Slots | |||
:empty_state |
Custom content displayed when no result matches. | ||
:footer |
Content at the bottom of the suggestions panel. | ||
:header |
Content at the top of the suggestions panel. | ||
:inner_prefix |
Content inside the control before the input. | ||
:inner_suffix |
Content inside the control after the input. | ||
:option |
Rich option content; receives `{label, value}`. | ||
:outer_prefix |
Content outside the control before it. | ||
:outer_suffix |
Content outside the control after it. | ||