Fluxon.Components.Select (Fluxon v2.3.4)
A modern, accessible select component for building rich selection interfaces in Phoenix LiveView.
The select component provides single and multiple selection, client-side and server-side search,
custom option rendering, grouped and nested options, and full keyboard navigation. It operates in
two modes: a feature-rich custom select (default) backed by a hidden <select> element for form
compatibility, and a lightweight native mode that renders the browser's built-in <select> control.
Select vs Autocomplete
Both components let users choose from a list of options, but they target different interaction patterns. Use this table to decide which fits your use case.
| Feature | Select | Fluxon.Components.Autocomplete |
|---|---|---|
| Primary interaction | Click to browse | Type to search |
| Best for | Small to medium lists | Any size dataset |
| Filtering | Client-side (+ optional server) | Client or server-side |
| Multiple selection | Yes | No |
| Clearable | Yes | Yes |
| Native mode | Yes | No |
Usage
Basic usage with a list of options:
<.select
name="country"
options={[{"United States", "us"}, {"Canada", "ca"}]}
/>The component follows the same options API as
Phoenix.HTML.Form.options_for_select/2,
supporting multiple formats:
# List of strings
<.select name="fruit" options={["Apple", "Banana", "Cherry"]} />
# List of tuples (label, value)
<.select name="country" options={[{"United States", "us"}, {"Canada", "ca"}]} />
# Keyword list
<.select name="lang" options={[English: "en", Spanish: "es", French: "fr"]} />
# Keyword list with key/value/disabled
<.select
name="role"
options={[
[key: "Admin", value: "admin"],
[key: "User", value: "user", disabled: true],
[key: "Guest", value: "guest"]
]}
/>
# Grouped options
<.select
name="city"
options={[
{"North America", [{"New York", "nyc"}, {"Toronto", "tor"}]},
{"Europe", [{"London", "lon"}, {"Paris", "par"}]}
]}
/>
# Multi-level nested groups (flattened with visual indentation)
<.select
name="city"
options={[
{"Europe", [
{"France", [{"Paris", 1}, {"Lyon", 2}]},
{"Italy", [{"Rome", 3}, {"Milan", 4}]}
]},
{"Asia", [
{"China", [{"Beijing", 5}, {"Shanghai", 6}]}
]}
]}
/>A full-featured example combining labels, description, help text, and placeholder:
<.select
name="payment_method"
label="Payment Method"
sublabel="Required"
description="Choose your preferred payment method"
help_text="We securely process all payment information"
placeholder="Select payment method"
options={[
{"Credit Card", "credit_card"},
{"PayPal", "paypal"},
{"Bank Transfer", "bank_transfer"},
{"Cryptocurrency", "crypto"}
]}
/>Native Select
Set native to render a standard HTML <select> element instead of the custom interface.
This is ideal for mobile-first designs, simple forms, or when maximum browser compatibility is required.
<.select
name="country"
native
placeholder="Pick a country"
options={[{"United States", "us"}, {"Canada", "ca"}]}
/>Native mode limitations
Features such as search, multiple selection, clearable, custom option rendering, header/footer slots, and affix slots are not available in native mode. Use the custom select for those capabilities.
Size Variants
The select component offers five size variants to fit different interface densities:
<.select name="xs" size="xs" options={@options} placeholder="Extra small" />
<.select name="sm" size="sm" options={@options} placeholder="Small" />
<.select name="md" size="md" options={@options} placeholder="Medium (default)" />
<.select name="lg" size="lg" options={@options} placeholder="Large" />
<.select name="xl" size="xl" options={@options} placeholder="Extra large" />| Size | Height | Use Case |
|---|---|---|
"xs" | h-7 | Compact UIs, dense layouts, inline controls |
"sm" | h-8 | Secondary inputs, sidebar filters |
"md" | h-9 | Default -- recommended for most forms |
"lg" | h-10 | Prominent inputs, settings pages |
"xl" | h-11 | Hero sections, onboarding flows |
Each size variant adjusts height, padding, font size, icon dimensions, and affix sizing proportionally.
Labels and Descriptions
Provide context and guidance with comprehensive labeling:
# Basic label
<.select name="country" label="Country" options={@countries} />
# Label with sublabel
<.select name="country" label="Country" sublabel="Required" options={@countries} />
# Label with description
<.select
name="country"
label="Country"
description="Where is your company headquartered?"
options={@countries}
/>
# Complete example with help text
<.select
name="country"
label="Country"
sublabel="Optional"
description="Used for tax calculation purposes"
help_text="You can change this later in settings"
placeholder="Select a country..."
options={@countries}
/>Form Integration
The select component integrates with Phoenix forms in two ways: using the field attribute for
full form integration or using the name attribute for standalone selects.
Using with Phoenix Forms (Recommended)
Use the field attribute to bind the select to a form field:
<.form :let={f} for={@changeset} phx-change="validate" phx-submit="save">
<.select
field={f[:country]}
label="Country"
options={@countries}
/>
</.form>Using the field attribute provides:
- Automatic value handling from form data
- Error handling and validation messages
- Form submission with correct field names
- Integration with changesets
- ID generation for accessibility
- Nested form data handling
Complete example with a changeset implementation:
defmodule MyApp.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :country, :string
field :languages, {:array, :string}
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:country, :languages])
|> validate_required([:country])
|> validate_subset(:languages, ["en", "es", "fr", "de"])
end
end
# In your LiveView
def mount(_params, _session, socket) do
countries = [{"United States", "us"}, {"Canada", "ca"}, {"Mexico", "mx"}]
languages = [{"English", "en"}, {"Spanish", "es"}, {"French", "fr"}, {"German", "de"}]
changeset = User.changeset(%User{}, %{})
{:ok, assign(socket, countries: countries, languages: languages, form: to_form(changeset))}
end
def render(assigns) do
~H"""
<.form :let={f} for={@form} phx-change="validate">
<.select clearable field={f[:country]} options={@countries} label="Country" />
<.select clearable multiple field={f[:languages]} options={@languages} label="Languages" />
</.form>
"""
end
def handle_event("validate", %{"user" => params}, socket) do
changeset =
%User{}
|> User.changeset(params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, form: to_form(changeset))}
endUsing Standalone Selects
For simpler cases or when not using Phoenix forms, use the name attribute:
<.select
name="sort_by"
value="newest"
options={[
{"Newest First", "newest"},
{"Oldest First", "oldest"},
{"Name A-Z", "name_asc"}
]}
/>When using standalone selects:
- The
nameattribute determines the form field name - Values are managed through the
valueattribute - Errors are passed via the
errorsattribute
Input States
# Disabled state
<.select name="locked" disabled options={@options} value="locked_value" />
# Error state (standalone)
<.select name="country" options={@countries} errors={["is required"]} />
# Error state (form integration -- errors are handled automatically)
<.select field={f[:country]} options={@countries} />Searchable
Enable the built-in search input to filter options by setting searchable:
<.select
name="country"
searchable
search_input_placeholder="Search for a country"
search_no_results_text="No countries found for %{query}"
options={[
{"United States", "us"},
{"Canada", "ca"},
{"Mexico", "mx"}
]}
/>Search Behavior
Client-side search provides immediate feedback with these characteristics:
- Case-insensitive matching
- Diacritics-insensitive (accents are ignored via Unicode normalization)
- Filters update as you type
- Full keyboard navigation remains active during search
- Search input receives focus automatically when the select opens
The search matches against option labels. For example, searching for "united" will match "United States" regardless of case or accents.
Custom Search Messages
Two attributes control the search text:
search_input_placeholder: Text shown in the search input (default:"Search...")search_no_results_text: Text shown when no options match (default:"No results found for %{query}.")
The no results message can include the search term with %{query}:
<.select
name="country"
searchable
search_input_placeholder="Find a country..."
search_no_results_text="No countries matching '%{query}'"
options={@countries}
/>Search with Multiple Selection
When combining search and multiple selection, the component preserves the selected state of filtered-out options. Selected labels remain visible in the toggle button even when those options are hidden by the search filter:
<.select
name="countries"
multiple
searchable
options={@countries}
placeholder="Select countries"
search_input_placeholder="Search countries..."
/>Search with Custom Options
Search matches against option labels while preserving your custom rendering:
<.select name="user" searchable options={@users}>
<:option :let={{label, value}}>
<div class="flex items-center gap-2 px-3 py-2">
<img src={avatar_url(value)} class="size-8 rounded-full" />
<div>
<div class="font-medium">{label}</div>
<div class="text-sm text-zinc-500">{user_role(value)}</div>
</div>
</div>
</:option>
</.select>Search Implementation Details
The client-side search:
- Runs entirely in JavaScript for immediate feedback
- Normalizes text to lowercase with NFD Unicode decomposition (strips diacritics)
- Matches against the full option label
- Updates the filtered list without changing the selected values
- Maintains all keyboard navigation features during search
Server-Side Search
For large datasets or dynamically computed results, enable server-side search by providing the
on_search attribute with a LiveView event name:
<.select
field={f[:user_id]}
searchable
search_threshold={2}
debounce={500}
on_search="search_users"
search_input_placeholder="Search users..."
search_no_results_text="No users found for '%{query}'"
options={@filtered_users}
/>When server-side search is enabled:
- The
on_searchevent is triggered after the debounce period - The event receives
%{"query" => query, "id" => component_id}as parameters - A loading spinner is displayed while the search is in progress
- The component reinitializes from the DOM once LiveView patches new options
Important: Selected Options and Search Results
This is a fundamental constraint of server-side search that requires careful consideration.
The toggle label is derived from the currently available options in the options attribute.
If a user has selected an option and then performs a search that filters it out, the toggle
label will become empty until the option reappears in the results.
Recommended implementation: Always include currently selected options in your server search results, even when they do not match the query. This prevents the toggle label from disappearing.
Search Configuration
| Attribute | Default | Description |
|---|---|---|
search_threshold | 0 | Minimum characters required before triggering a search |
debounce | 300 | Milliseconds to wait after typing stops before searching |
on_search | nil | LiveView event name; when set, enables server-side search |
Example with all search options configured:
<.select
field={f[:product_id]}
searchable
search_threshold={3}
debounce={400}
on_search="search_products"
search_input_placeholder="Type at least 3 characters to search..."
search_no_results_text="No products found matching '%{query}'"
options={@products}
/>Multiple Selection
Enable multiple selection with the multiple attribute:
<.select
name="countries"
multiple
placeholder="Select countries"
options={[{"United States", "us"}, {"Canada", "ca"}, {"Mexico", "mx"}]}
/>This allows selecting multiple options and submits an array of values:
%{"_target" => ["countries"], "countries" => ["", "us", "ca", "mx"]}Empty values in multiple select
Per the HTML specification, a <select multiple> with no selected options omits the field
from form data entirely. To ensure the field is always present, the component injects a
hidden input with an empty value:
<input type="hidden" name="countries[]" value="" />
<select multiple name="countries[]">
<!-- options here -->
</select>This results in consistent form submissions:
# No options selected
%{"countries" => [""]}
# One or more selected
%{"countries" => ["", "us", "ca"]}When processing form data, filter out the empty string:
selected = Enum.reject(params["countries"] || [], &(&1 == ""))By default, there is no limit on the number of selections.
Maximum Selections
Use the max attribute to cap the number of selections:
<.select name="tags" multiple max={3} options={@tags} placeholder="Select up to 3 tags" />When the maximum is reached, additional options become unselectable until an existing selection is removed.
Clearable
By default, single selections cannot be unselected. The clearable attribute adds a clear
button and enables re-clicking a selected option to deselect it. Pressing Backspace when the
toggle is focused also clears the selection. For multiple selections, the clear button removes
all selections at once.
<.select name="country" clearable options={@countries} placeholder="Select a country" />When clearing a single selection, the empty placeholder option (<option value="">) is selected,
sending an empty string in the form data -- consistent with how multiple select handles empty states.
Disabled Options
Individual options can be disabled using the keyword list format with a disabled key:
<.select
name="role"
options={[
[key: "Admin", value: "admin"],
[key: "Editor", value: "editor", disabled: true],
[key: "Viewer", value: "viewer"]
]}
/>Disabled options:
- Cannot be selected via click or keyboard
- Are skipped during arrow key navigation
- Are skipped during typeahead matching
- Display with reduced opacity and a
not-allowedcursor - Have
aria-disabled="true"anddata-disabledattributes
Disabled options also work within grouped options:
<.select
name="user"
options={[
{"Active", [
[key: "Alice", value: "alice"],
[key: "Bob", value: "bob"]
]},
{"Inactive", [
[key: "Charlie", value: "charlie", disabled: true],
[key: "Diana", value: "diana", disabled: true]
]}
]}
/>Inner Affixes
Add content inside the select's border using inner affix slots. Useful for icons, status indicators, and visual elements:
<.select name="category" options={@categories} placeholder="Select category">
<:inner_prefix>
<.icon name="hero-folder" class="icon" />
</:inner_prefix>
</.select>
<.select name="status" options={@statuses} placeholder="Select status">
<:inner_prefix>
<.icon name="hero-flag" class="icon" />
</:inner_prefix>
<:inner_suffix>
<.icon name="hero-check-circle" class="icon text-green-500!" />
</:inner_suffix>
</.select>Inner Suffix Replaces Chevron
When you provide an inner_suffix slot, it replaces the default chevron down arrow.
The chevron is only shown when no inner_suffix is provided, maintaining the
expected select behavior while allowing customization.
Outer Affixes
Place content outside the select's border using outer affix slots. Ideal for buttons, labels, and interactive elements:
<.select name="filtered" options={@options} placeholder="Select option">
<:outer_prefix class="px-3 text-foreground-soft">Filter:</:outer_prefix>
</.select>
<.select name="with_action" options={@options} placeholder="Select option">
<:outer_suffix>
<.button size="md">Apply</.button>
</:outer_suffix>
</.select>Size Matching with Affixes
When using buttons or components within affix slots, match their
sizeattribute to the select'ssizefor proper visual alignment:<.select name="example" size="lg" options={@options}> <:outer_suffix> <.button size="lg">Action</.button> </:outer_suffix> </.select>
Custom Option Rendering
Customize how options appear in the dropdown through the :option slot. The component passes a
{label, value} tuple to the slot via :let:
<.select
name="role"
placeholder="Select role"
options={[
{"Admin", "admin"},
{"Editor", "editor"},
{"Viewer", "viewer"}
]}
>
<:option :let={{label, value}}>
<div class={[
"flex items-center justify-between",
"rounded-lg py-2 px-3",
"in-data-selected:bg-zinc-100",
"in-data-highlighted:bg-zinc-50"
]}>
<div>
<div class="font-medium text-sm">{label}</div>
<div class="text-zinc-500 text-xs">
{case value do
"admin" -> "Full access to all features"
"editor" -> "Can create and modify content"
"viewer" -> "Read-only access to content"
end}
</div>
</div>
<.icon :if={value == "admin"} name="u-shield-check" class="size-4 text-blue-500" />
</div>
</:option>
</.select>Toggle Label
The toggle button always displays the option's text label regardless of custom rendering.
For example, an option {"Admin User", "admin"} will show "Admin User" in the toggle when
selected, even if the dropdown renders it with an avatar and description.
Option States
Custom options can respond to these data attributes for state-aware styling:
| Attribute | Applied When | Use Case |
|---|---|---|
[data-highlighted] | Option is highlighted via keyboard or mouse hover | Hover/focus background |
[data-selected] | Option is currently selected | Checkmark, bold text, accent color |
[data-disabled] | Option is disabled | Reduced opacity, not-allowed cursor |
Use Tailwind's in-data-* variants to style ancestor-aware states:
<.select name="country" options={@countries}>
<:option :let={{label, value}}>
<div class={[
"flex items-center gap-2 px-3 py-2 cursor-default select-none",
"in-data-highlighted:bg-zinc-100",
"in-data-selected:font-medium",
"in-data-disabled:opacity-50"
]}>
<.icon name={"flag-#{String.downcase(value)}"} class="size-5" />
<span>{label}</span>
</div>
</:option>
</.select>Header and Footer Content
Add custom content at the top and bottom of the listbox using the :header and :footer slots.
Useful for filter buttons, action links, or informational text:
<.select field={f[:product]} options={@products}>
<:header class="p-2 border-b">
<div class="flex gap-2">
<.button size="xs" class="rounded-full" phx-click="filter" phx-value-type="all">All</.button>
<.button size="xs" class="rounded-full" phx-click="filter" phx-value-type="active">Active</.button>
</div>
</:header>
<:footer class="p-2 border-t">
<.button type="button" size="sm" class="w-full" as="link" navigate={~p"/products/new"}>
<.icon name="u-plus" class="size-4" /> Create new
</.button>
</:footer>
</.select>Both slots accept a class attribute for custom styling.
Keyboard Navigation
The select component implements comprehensive keyboard navigation following WAI-ARIA listbox patterns:
| Key | Context | Description |
|---|---|---|
Tab / Shift+Tab | Toggle button | Moves focus to and from the select |
Space / Enter | Toggle button | Opens the listbox; if open, selects highlighted option |
Down Arrow | Toggle (closed) | Opens the listbox and highlights the first option (or last selected) |
Up Arrow | Toggle (closed) | Opens the listbox and highlights the last option (or last selected) |
Down Arrow | Listbox open | Moves highlight to the next visible, non-disabled option |
Up Arrow | Listbox open | Moves highlight to the previous visible, non-disabled option |
Home | Listbox open | Moves highlight to the first visible, non-disabled option |
End | Listbox open | Moves highlight to the last visible, non-disabled option |
Enter | Listbox open | Selects the highlighted option (closes for single, stays open for multiple) |
Escape | Listbox open | Closes the listbox and returns focus to the toggle |
Backspace | Toggle button | Clears the selection (only when clearable is enabled) |
| Any printable character | Toggle / Listbox | Typeahead: finds and highlights the first matching option |
When searchable is enabled, the search input receives focus when the listbox opens, and all
keyboard navigation keys continue to work from the search input.
For multiple selection mode:
Entertoggles the selection state without closing the listbox- The listbox stays open for additional choices
- Focus is maintained on the search input (when searchable) after each selection
Typeahead
When the listbox is closed, typing characters selects the matching option directly. When open, typing highlights the match. Characters accumulate for 300ms, then reset. Disabled options are skipped during typeahead.
<.form :let={f} for={@form} phx-change="validate">
<.select field={f[:country]} options={@countries} label="Country" searchable />
</.form>def handle_event("add_country", %{"country" => params}, socket) do
updated_countries = [{params["name"], params["code"]} | socket.assigns.countries]
{:noreply, assign(socket, :countries, updated_countries)}
end<.select
name="custom_anim"
options={@options}
animation="transition duration-200 ease-out"
animation_enter="opacity-100 translate-y-0"
animation_leave="opacity-0 -translate-y-1"
/>Real-World Examples
Cascading Selects
Dependent select fields where selecting a value in one field determines the options in the next:
<.form :let={f} for={@location} phx-change="update">
<.select
field={f[:country]}
options={@countries}
label="Country"
placeholder="Select a country..."
clearable
/>
<.select
field={f[:state]}
options={@states}
label="State"
placeholder="Select a state..."
disabled={@states == []}
clearable
/>
<.select
field={f[:city]}
options={@cities}
label="City"
placeholder="Select a city..."
disabled={@cities == []}
/>
</.form>def mount(_params, _session, socket) do
{:ok,
assign(socket,
countries: [{"United States", "us"}, {"Brazil", "br"}],
states: [],
cities: [],
location: to_form(%{})
)}
end
def handle_event("update", %{"location" => params}, socket) do
case params do
%{"country" => country} when country != "" ->
states = fetch_states_for_country(country)
{:noreply, assign(socket, states: states, cities: [], location: to_form(params))}
%{"country" => country, "state" => state} when country != "" and state != "" ->
cities = fetch_cities_for_state(state)
{:noreply, assign(socket, cities: cities, location: to_form(params))}
_ ->
{:noreply, assign(socket, states: [], cities: [], location: to_form(params))}
end
endUser Assignment with Avatars
<.select field={f[:assigned_to]} searchable options={@users} placeholder="Assign to...">
<:inner_prefix>
<.icon name="hero-user" class="icon" />
</:inner_prefix>
<:option :let={{label, value}}>
<div class="flex items-center gap-3 p-2">
<img src={"https://i.pravatar.cc/150?u=#{value}"} class="size-8 rounded-full" />
<div>
<div class="font-medium">{label}</div>
<div class="text-sm text-zinc-500">{user_email(value)}</div>
</div>
</div>
</:option>
</.select>Tag Picker with Maximum Limit
<.select
field={f[:tags]}
multiple
max={5}
searchable
clearable
label="Tags"
sublabel="Up to 5"
placeholder="Select tags..."
search_input_placeholder="Search tags..."
options={@available_tags}
/>Product Filter with Header Actions
<.select name="product" options={@products} placeholder="Filter products...">
<:header class="p-2 border-b">
<div class="flex gap-2">
<.button size="xs" phx-click="filter" phx-value-category="all">All</.button>
<.button size="xs" phx-click="filter" phx-value-category="active">Active</.button>
<.button size="xs" phx-click="filter" phx-value-category="archived">Archived</.button>
</div>
</:header>
<:footer class="p-2 border-t">
<.button size="sm" class="w-full" as="link" navigate={~p"/products/new"}>
<.icon name="u-plus" class="size-4" /> Add Product
</.button>
</:footer>
</.select>Server-Side Search with Preselected Value
<.select
field={f[:customer_id]}
searchable
on_search="search_customers"
search_threshold={2}
debounce={400}
clearable
label="Customer"
search_input_placeholder="Type to search customers..."
search_no_results_text="No customers matching '%{query}'"
options={@customer_options}
/>def handle_event("search_customers", %{"query" => query, "id" => _id}, socket) do
selected_id = socket.assigns.form[:customer_id].value
results = MyApp.Customers.search(query)
# Always include the currently selected customer to prevent label disappearing
options =
if selected_id do
selected = MyApp.Customers.get!(selected_id)
[{selected.name, selected.id} | results] |> Enum.uniq_by(&elem(&1, 1))
else
results
end
{:noreply, assign(socket, customer_options: options)}
end
Summary
Components
Renders a select component with rich features and full keyboard navigation support.
Components
Renders a select component with rich features and full keyboard navigation support.
This component provides a flexible way to build selection interfaces, from simple dropdowns to complex searchable multi-select fields with custom option rendering. Use it whenever users need to choose one or more values from a predefined list. It includes built-in form integration, error handling, and accessibility features following WAI-ARIA listbox patterns.
In its default custom mode, a hidden <select> element is maintained in sync for form
submissions. Set native to render the browser's built-in <select> instead.
Attributes
id(:any) - Sets the unique identifier for the select component. When not provided, defaults to thenameattribute value. Controls the IDs of the wrapper ({id}-wrapper), toggle button ({id}-toggle), and listbox ({id}-listbox).Defaults to
nil.name(:any) - Specifies the form field name for the select. Required when not using thefieldattribute. For multiple selections, the name is automatically suffixed with[]to submit an array.field(Phoenix.HTML.FormField) - Binds the select to a Phoenix form field. When provided, the component automatically extracts the name, id, value, and errors from the form data, and handles changeset integration. Takes precedence overname,value, anderrorsattributes.native(:boolean) - Controls whether to render a native HTML<select>element instead of the custom select. Native mode is useful for mobile interfaces where platform controls are preferred, or for simple selection needs. Features like search, multiple selection, clearable, custom option rendering, and affix slots are not available in native mode.Defaults to
false.class(:any) - Specifies additional CSS classes to apply to the select. For the custom select, classes are applied to the listbox container. For native selects, classes are applied to the<select>element itself.Defaults to
nil.label(:string) - Sets the primary label text displayed above the select. Renders as an HTML<label>element linked to the toggle button for accessibility.Defaults to
nil.sublabel(:string) - Specifies secondary text displayed alongside the main label. Use for contextual hints like "Required", "Optional", or "Beta" without cluttering the primary label.Defaults to
nil.help_text(:string) - Sets help text displayed below the select field. Use for supplementary guidance or instructions that assist the user in making a selection.Defaults to
nil.description(:string) - Specifies a longer description displayed below the label but above the select element. Provides additional context about what the field is used for or how the selection will be applied.Defaults to
nil.placeholder(:string) - Sets the text displayed when no option is selected. Appears as a pseudo-element via CSS when the toggle label is empty. In native mode, renders as a disabled placeholder<option>.Defaults to
nil.searchable(:boolean) - Controls whether a search input is shown at the top of the listbox for filtering options. Search is case-insensitive and diacritics-insensitive. When enabled, the search input automatically receives focus when the listbox opens. Only available in custom select mode.Defaults to
false.disabled(:boolean) - Controls whether the select is disabled. Disabled selects cannot be interacted with, appear visually muted, and are excluded from form submission.Defaults to
false.size(:string) - Controls the visual size of the select component. Adjusts height, padding, font size, and icon dimensions proportionally."xs"-- Extra small (h-7), suitable for compact UIs and dense layouts"sm"-- Small (h-8), good for secondary inputs and sidebar filters"md"-- Medium (h-9), the default size recommended for most forms"lg"-- Large (h-10), suitable for prominent inputs and settings pages"xl"-- Extra large (h-11), ideal for hero sections and onboarding flows
Defaults to
"md".search_input_placeholder(:string) - Sets the placeholder text for the search input whensearchableis enabled. Guides the user on what to type to filter options.Defaults to
"Search...".search_no_results_text(:string) - Specifies the message displayed when no options match the search query. Use%{query}as a placeholder token that will be replaced with the actual search term wrapped in<strong>tags.Defaults to
"No results found for %{query}.".search_threshold(:integer) - Sets the minimum number of characters required before filtering options or triggering a server-side search. Useful for preventing unnecessary operations on very short queries. A value of0means filtering starts immediately on the first character.Defaults to
0.debounce(:integer) - Controls the debounce time in milliseconds for server-side searches. Delays theon_searchevent to avoid excessive server calls while the user is typing. Only applies whenon_searchis set.Defaults to
300.on_search(:string) - Specifies the LiveView event name to trigger for server-side searching. When set, the component dispatches afluxon:select:searchcustom event that the hook translates into apushEvent(on_search, %{"query" => query, "id" => id})call. The server should update theoptionsassign with the filtered results.Defaults to
nil.multiple(:boolean) - Controls whether multiple options can be selected simultaneously. When enabled, the hidden<select>gains themultipleattribute, the toggle displays comma-separated labels, and selecting an option toggles it without closing the listbox. Not available in native mode.Defaults to
false.value(:any) - Sets the currently selected value or values. For single selection, pass a string or atom. For multiple selection, pass a list of values. When using thefieldattribute, this is automatically extracted from the form data.errors(:list) - Specifies a list of error messages to display below the select. When present, the toggle button receives adata-invalidattribute for error styling. Errors are automatically extracted when using thefieldattribute with form validation.Defaults to
[].options(:list) (required) - Specifies the list of options for the select. Supports multiple formats following thePhoenix.HTML.Form.options_for_select/2API:- Strings:
["Apple", "Banana"]-- label and value are the same - Tuples:
[{"Label", "value"}]-- explicit label/value pairs - Atoms:
[:admin, :user]-- converted to strings for both label and value - Integers:
[1, 2, 3]-- converted to strings - Keyword lists:
[key: "Admin", value: "admin"]-- with optionaldisabled: true - Keyword pairs:
[Admin: "admin", User: "user"] - Grouped:
[{"Group Label", [nested_options]}]-- supports unlimited nesting depth
- Strings:
max(:integer) - Sets the maximum number of options that can be selected whenmultipleis enabled. When the limit is reached, additional options become unselectable until an existing selection is removed. Defaults tonil(unlimited).Defaults to
nil.clearable(:boolean) - Controls whether selections can be cleared. When enabled, displays a clear button (X icon) in the toggle, allows re-clicking a selected option to deselect it in single-select mode, and enablesBackspaceto clear the selection from the toggle. For multiple select, clears all selections at once.Defaults to
false.include_hidden(:boolean) - Controls whether a hidden<input>is rendered for multiple selects. Ensures the field is always present in form submissions, even when no option is selected. Set tofalseonly if you handle empty submissions separately.Defaults to
true.animation(:string) - Sets the base CSS transition classes for the listbox open/close animation. Defaults to"transition duration-150 ease-in-out".animation_enter(:string) - Sets the CSS classes applied to the listbox when fully visible (enter animation end state). Defaults to"opacity-100 scale-100".animation_leave(:string) - Sets the CSS classes applied to the listbox when closing (leave animation end state). Defaults to"opacity-0 scale-95".Global attributes are accepted. Specifies additional HTML attributes passed through to the underlying
<select>element. Supports theformattribute to associate with a form by ID. Supports all globals plus:["form"].
Slots
option- Defines custom rendering for each option in the listbox. Receives a{label, value}tuple via:let. When not provided, options render with the default layout (label text plus a checkmark icon for selected state). Custom options can usein-data-highlighted,in-data-selected, andin-data-disabledTailwind variants for state-aware styling.Accepts attributes:class(:any) - Specifies additional CSS classes for the option wrapper.
toggle- Defines custom rendering for the select's trigger button. When provided, replaces the default toggle layout entirely. Use for highly customized trigger designs that don't fit the standard label-with-chevron pattern.Accepts attributes:class(:any) - Specifies additional CSS classes for the toggle wrapper.
header- Renders custom content at the top of the listbox, above the options list. Useful for filter buttons, category tabs, or informational banners. Content appears below the search input whensearchableis enabled.Accepts attributes:class(:any) - Specifies additional CSS classes for the header container.
footer- Renders custom content at the bottom of the listbox, below the options list. Ideal for "Create new" links, summary text, or action buttons.Accepts attributes:class(:any) - Specifies additional CSS classes for the footer container.
inner_prefix- Renders content inside the select field's border, before the selected value display. Ideal for icons or short textual prefixes that should appear within the input area. Icon sizing is automatically adjusted based on the select'ssizeattribute.Accepts attributes:class(:any) - Specifies CSS classes for the inner prefix container.
outer_prefix- Renders content outside and before the select field's border. Suitable for standalone labels, dropdown menus, or buttons that visually attach to the select's left edge. When containing aFluxon.Components.Button, the border is automatically hidden to avoid double borders.Accepts attributes:class(:any) - Specifies CSS classes for the outer prefix container.
inner_suffix- Renders content inside the select field's border, after the selected value display. Takes precedence over the default chevron icon -- when this slot is provided, the chevron is hidden. Suitable for status icons, validation indicators, or custom dropdown arrows.Accepts attributes:class(:any) - Specifies CSS classes for the inner suffix container.
outer_suffix- Renders content outside and after the select field's border. Ideal for action buttons, submit controls, or links that visually attach to the select's right edge. When containing aFluxon.Components.Button, the border is automatically hidden to avoid double borders.Accepts attributes:class(:any) - Specifies CSS classes for the outer suffix container.