Fluxon.Components.Autocomplete (Fluxon v2.3.4)

A type-to-search input that filters and selects from a list of options, with client-side and server-side search support, keyboard navigation, and full Phoenix form integration.

The autocomplete component provides a combobox-style text input where users type to filter a dropdown of options. It is optimized for search-driven selection workflows of any dataset size, and offers customizable search modes, debounced server-side search, animated transitions, grouped options with nesting, custom option rendering, and rich empty-state handling.

Autocomplete vs Select

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.

FeatureSelectAutocomplete
Primary interactionClick to browseType to search
Best forSmall to medium listsAny size dataset
FilteringClient-side (+ optional server)Client or server-side
Multiple selectionYesNo
ClearableYesYes
Native modeYesNo
Custom empty stateNoYes (:empty_state slot)
Open on focusAlways (click to open)Optional (open_on_focus)

Usage

Basic usage with a list of options:

<.autocomplete
  name="country"
  options={[{"United States", "us"}, {"Canada", "ca"}]}
  placeholder="Search countries..."
/>

The component follows the same options API as Phoenix.HTML.Form.options_for_select/2, supporting multiple formats:

# List of strings
<.autocomplete name="fruit" options={["Apple", "Banana", "Cherry"]} />

# List of tuples (label, value)
<.autocomplete name="country" options={[{"United States", "us"}, {"Canada", "ca"}]} />

# Keyword list
<.autocomplete name="lang" options={[English: "en", Spanish: "es", French: "fr"]} />

# Keyword list with key/value/disabled
<.autocomplete
  name="role"
  options={[
    [key: "Admin", value: "admin"],
    [key: "User", value: "user", disabled: true],
    [key: "Guest", value: "guest"]
  ]}
/>

# Grouped options
<.autocomplete
  name="city"
  options={[
    {"North America", [{"New York", "nyc"}, {"Toronto", "tor"}]},
    {"Europe", [{"London", "lon"}, {"Paris", "par"}]}
  ]}
/>

# Multi-level nested groups (flattened with visual indentation)
<.autocomplete
  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:

<.autocomplete
  name="deployment"
  label="Deployment Platform"
  sublabel="Optional"
  description="This will determine your deployment configuration"
  help_text="Choose from our supported platforms"
  placeholder="Select platform..."
  options={["Fly.io", "Railway", "Heroku", "Render", "AWS"]}
/>

Size Variants

The autocomplete component offers five size variants to fit different interface densities:

<.autocomplete name="xs" size="xs" options={@options} placeholder="Extra small" />
<.autocomplete name="sm" size="sm" options={@options} placeholder="Small" />
<.autocomplete name="md" size="md" options={@options} placeholder="Medium (default)" />
<.autocomplete name="lg" size="lg" options={@options} placeholder="Large" />
<.autocomplete name="xl" size="xl" options={@options} placeholder="Extra large" />
SizeHeightUse Case
"xs"h-7Compact UIs, dense layouts, inline controls
"sm"h-8Secondary inputs, sidebar filters
"md"h-9Default -- recommended for most forms
"lg"h-10Prominent inputs, settings pages
"xl"h-11Hero 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
<.autocomplete name="language" label="Language" options={@languages} />

# Label with sublabel
<.autocomplete name="language" label="Language" sublabel="Required" options={@languages} />

# Label with description
<.autocomplete
  name="language"
  label="Language"
  description="Choose the primary language for your project"
  options={@languages}
/>

# Complete example with help text
<.autocomplete
  name="language"
  label="Language"
  sublabel="Optional"
  description="Used for localization purposes"
  help_text="You can change this later in settings"
  placeholder="Search languages..."
  options={@languages}
/>

Form Integration

The autocomplete component integrates with Phoenix forms in two ways: using the field attribute for full form integration or using the name attribute for standalone inputs.

Use the field attribute to bind the autocomplete to a form field:

<.form :let={f} for={@changeset} phx-change="validate" phx-submit="save">
  <.autocomplete
    field={f[:assigned_to_id]}
    label="Assigned To"
    options={@users}
  />
</.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

Complete example with a changeset implementation:

defmodule MyApp.Task do
  use Ecto.Schema
  import Ecto.Changeset

  schema "tasks" do
    field :title, :string
    belongs_to :assigned_to, MyApp.User
    timestamps()
  end

  def changeset(task, attrs) do
    task
    |> cast(attrs, [:assigned_to_id, :title])
    |> validate_required([:assigned_to_id])
  end
end

# In your LiveView
def mount(_params, _session, socket) do
  users = MyApp.Accounts.list_active_users() |> Enum.map(&{&1.name, &1.id})
  changeset = Task.changeset(%Task{}, %{})

  {:ok, assign(socket, users: users, form: to_form(changeset))}
end

def render(assigns) do
  ~H"""
  <.form :let={f} for={@form} phx-change="validate">
    <.autocomplete
      field={f[:assigned_to_id]}
      options={@users}
      label="Assigned To"
      placeholder="Search users..."
    />
  </.form>
  """
end

def handle_event("validate", %{"task" => params}, socket) do
  changeset =
    %Task{}
    |> Task.changeset(params)
    |> Map.put(:action, :validate)

  {:noreply, assign(socket, form: to_form(changeset))}
end

Using Standalone Autocomplete

For simpler cases or when not using Phoenix forms, use the name attribute:

<.autocomplete
  name="search_user"
  options={@users}
  value={@user_id}
  placeholder="Search users..."
/>

When using standalone autocomplete:

  • The name attribute determines the form field name
  • Values are managed through the value attribute
  • Errors are passed via the errors attribute

Input States

# Disabled state
<.autocomplete name="locked" disabled options={@options} value="locked_value" />

# Error state (standalone)
<.autocomplete name="country" options={@countries} errors={["is required"]} />

# Error state (form integration -- errors are handled automatically)
<.autocomplete field={f[:country]} options={@countries} />

Search Behavior

The autocomplete component offers flexible search strategies to handle different use cases.

Client-side search provides immediate feedback as users type, filtering the provided options directly in the browser. This is the default behavior (no on_search attribute).

Search Modes

Three search modes control how options are matched against the search query:

ModeBehaviorUse Case
"contains"Matches anywhere in the option labelGeneral-purpose search (default)
"starts-with"Matches only at the beginning of the labelAlphabetical browsing, name lookup
"exact"Requires the query to match the label exactlyPrecise matching, code lookup
<.autocomplete
  name="search"
  search_mode="starts-with"
  options={["JavaScript", "TypeScript", "CoffeeScript"]}
/>

Search Implementation Details

The client-side search:

  • Runs entirely in JavaScript for immediate feedback
  • Is case-insensitive (normalizes to lowercase)
  • Matches against the full option label text content
  • Hides non-matching options without removing them from the DOM
  • Automatically highlights the first matching option

Search Threshold

Control when filtering begins based on the number of characters typed:

# No threshold -- search starts on first character (default)
<.autocomplete name="search" search_threshold={0} options={@options} />

# Require at least 2 characters before searching
<.autocomplete name="search" search_threshold={2} options={@options} />

For large datasets or dynamically computed results, enable server-side search by providing the on_search attribute with a LiveView event name:

<.autocomplete
  field={f[:user_id]}
  on_search="search_users"
  search_threshold={2}
  debounce={500}
  placeholder="Search users..."
  options={@filtered_users}
/>

When server-side search is enabled:

  • The on_search event 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
def handle_event("search_users", %{"query" => query}, socket) do
  users = MyApp.Accounts.search(query) |> Enum.map(&{&1.name, &1.id})
  {:noreply, assign(socket, filtered_users: users)}
end

Important: Selected Options and Search Results

This is a fundamental constraint of server-side search that requires careful consideration.

The input label is derived from the currently available options in the options attribute. If a user has selected an option and then the server responds with results that do not include it, the label may become stale until the option reappears.

Recommended implementation: Always include the currently selected option in your server search results, even when it does not match the query. This prevents the label from disappearing.

Search Configuration

AttributeDefaultDescription
search_threshold0Minimum characters required before triggering a search
debounce200Milliseconds to wait after typing stops before searching
on_searchnilLiveView event name; when set, enables server-side search
search_mode"contains"Client-side matching strategy ("contains", "starts-with", "exact")

Clearable

The clearable attribute adds a clear button (X icon) inside the input that appears when a value is selected. Clicking it resets both the visible input and the hidden form value:

<.autocomplete name="country" clearable options={@countries} placeholder="Select a country" />

When the autocomplete is disabled, the clear button is hidden regardless of the clearable setting.

Disabled Options

Individual options can be disabled using the keyword list format with a disabled key:

<.autocomplete
  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 keyboard navigation
  • Display with reduced opacity and a not-allowed cursor
  • Have aria-disabled="true" and data-disabled attributes

Disabled options also work within grouped options:

<.autocomplete
  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]
    ]}
  ]}
/>

Open on Focus

By default, the listbox opens when users start typing. Enable open_on_focus to show options immediately when the input is focused, even before any characters are typed:

<.autocomplete
  name="country"
  open_on_focus
  placeholder="Click to see all options..."
  options={@countries}
/>

open_on_focus and search_threshold interaction

The listbox only opens on focus if the current query length meets the search_threshold. With the default threshold of 0, the listbox opens immediately. With search_threshold={2}, the user must type at least 2 characters before focus-triggered opening takes effect.

Inner Affixes

Add content inside the autocomplete's border using inner affix slots. Useful for icons, status indicators, and visual elements:

<.autocomplete name="user" options={@users} placeholder="Search users...">
  <:inner_prefix>
    <.icon name="hero-magnifying-glass" class="icon" />
  </:inner_prefix>
</.autocomplete>

<.autocomplete name="email" options={@emails} placeholder="user@example.com">
  <:inner_prefix>
    <.icon name="hero-at-symbol" class="icon" />
  </:inner_prefix>
  <:inner_suffix>
    <.icon name="hero-check-circle" class="icon text-green-500!" />
  </:inner_suffix>
</.autocomplete>

Outer Affixes

Place content outside the autocomplete's border using outer affix slots. Ideal for buttons, labels, and interactive elements:

<.autocomplete name="filtered" options={@options} placeholder="Search...">
  <:outer_prefix class="px-3 text-foreground-soft">Filter:</:outer_prefix>
</.autocomplete>

<.autocomplete name="with_action" options={@options} placeholder="Search...">
  <:outer_suffix>
    <.button size="md">Search</.button>
  </:outer_suffix>
</.autocomplete>

Size Matching with Affixes

When using buttons or components within affix slots, match their size attribute to the autocomplete's size for proper visual alignment:

<.autocomplete name="example" size="lg" options={@options}>
  <:outer_suffix>
    <.button size="lg">Action</.button>
  </:outer_suffix>
</.autocomplete>

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:

<.autocomplete
  name="user"
  placeholder="Search users..."
  options={[{"Alice Chen", "alice"}, {"Bob Smith", "bob"}]}
>
  <:option :let={{label, value}}>
    <div class={[
      "flex items-center gap-3 p-2 rounded-lg",
      "in-data-highlighted:bg-zinc-100",
      "in-data-selected:font-medium"
    ]}>
      <img src={"/avatars/#{value}.jpg"} class="size-8 rounded-full" />
      <div>
        <div class="text-sm font-medium">{label}</div>
        <div class="text-xs text-zinc-500">@{value}</div>
      </div>
    </div>
  </:option>
</.autocomplete>

Input Label

The text input always displays the option's text label regardless of custom rendering. For example, an option {"Alice Chen", "alice"} will show "Alice Chen" in the input 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:

AttributeApplied WhenUse Case
[data-highlighted]Option is highlighted via keyboard or mouse hoverHover/focus background
[data-selected]Option is currently selectedCheckmark, bold text, accent color
[data-disabled]Option is disabledReduced opacity, not-allowed cursor

Use Tailwind's in-data-* variants to style ancestor-aware states:

<.autocomplete 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>
</.autocomplete>

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:

<.autocomplete 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>
</.autocomplete>

Both slots accept a class attribute for custom styling.

Empty State Customization

When no options match the search query, the component displays an empty state. There are two ways to customize it:

Default Text Message

Use the no_results_text attribute. The %{query} token is replaced with the search term wrapped in <strong> tags:

<.autocomplete
  name="search"
  no_results_text="No countries matching '%{query}'"
  options={@countries}
/>

Custom Empty State Slot

For richer empty states with icons, buttons, or complex layouts, use the :empty_state slot. When provided, it replaces the default text message:

<.autocomplete name="search" options={@items}>
  <:empty_state>
    <div class="p-4 text-center">
      <p class="font-medium text-zinc-500">No results found</p>
      <p class="text-sm text-zinc-400">Try a different search term</p>
    </div>
  </:empty_state>
</.autocomplete>

Keyboard Navigation

The autocomplete component implements keyboard navigation following WAI-ARIA combobox patterns:

KeyContextDescription
Tab / Shift+TabInputMoves focus to and from the input
Down ArrowInput (closed)Opens the listbox and highlights the first option (or selected option)
Up ArrowInput (closed)Opens the listbox and highlights the last option (or selected option)
Down ArrowListbox openMoves highlight to the next visible, non-disabled option
Up ArrowListbox openMoves highlight to the previous visible, non-disabled option
EnterListbox openSelects the highlighted option and closes the listbox
EscapeListbox openCloses the listbox and returns focus to the input
Type charactersInputFilters options based on typed text
Click outsideAnyCloses the listbox

Highlight does not wrap

Unlike the select component, arrow key navigation in the autocomplete does not wrap around. Pressing Down Arrow on the last option stays on the last option, and Up Arrow on the first option stays on the first option.

Real-World Examples

Task Assignment with Avatars

<.autocomplete
  field={f[:assigned_to_id]}
  label="Assign To"
  description="Select a team member to assign this task"
  placeholder="Search by name..."
  clearable
  options={@users}
>
  <: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>
</.autocomplete>

Server-Side Search with Preselected Value

<.autocomplete
  field={f[:customer_id]}
  on_search="search_customers"
  search_threshold={2}
  debounce={400}
  clearable
  label="Customer"
  placeholder="Type to search customers..."
  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

E-commerce Product Search with Groups

<.autocomplete
  name="product"
  label="Product"
  sublabel="Required"
  help_text="Start typing to search through 1000+ products"
  placeholder="Search products..."
  clearable
  search_threshold={3}
  options={[
    {"Electronics", [
      {"iPhone 15 Pro", "iphone15pro"},
      {"MacBook Air M2", "macbook-air-m2"}
    ]},
    {"Clothing", [
      {"Men's T-Shirt", "mens-tshirt"},
      {"Winter Jacket", "winter-jacket"}
    ]}
  ]}
>
  <:inner_prefix>
    <.icon name="hero-magnifying-glass" class="icon" />
  </:inner_prefix>
  <:outer_suffix>
    <.button size="md">Browse</.button>
  </:outer_suffix>
</.autocomplete>

Custom Empty State with Create Action

<.autocomplete
  field={f[:tag_id]}
  label="Tag"
  placeholder="Search tags..."
  options={@tags}
>
  <:empty_state>
    <div class="p-4 text-center">
      <p class="font-medium text-zinc-500">No matching tags found</p>
      <.button type="button" size="sm" class="mt-2" phx-click="create_tag">
        <.icon name="u-plus" class="size-4" /> Create new tag
      </.button>
    </div>
  </:empty_state>
</.autocomplete>
defmodule MyApp.UsersLive do
  use MyAppWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok, assign(socket, users: [], form: to_form(%{}, as: :assignment))}
  end

  def render(assigns) do
    ~H\"""
    <.form :let={f} for={@form} phx-change="validate">
      <.autocomplete
        field={f[:user_id]}
        options={Enum.map(@users, &{&1.name, &1.id})}
        on_search="search_users"
        search_threshold={2}
        debounce={300}
        clearable
        label="Assign to"
        placeholder="Search users..."
      >
        <:option :let={{label, value}}>
          <div class="flex items-center gap-3 p-2 in-data-highlighted:bg-zinc-100">
            <img src={user_avatar(value, @users)} class="size-8 rounded-full" />
            <div>
              <div class="font-medium text-sm">{label}</div>
              <div class="text-xs text-zinc-500">{user_email(value, @users)}</div>
            </div>
          </div>
        </:option>
        <:empty_state>
          <div class="p-4 text-center text-zinc-500">
            <p>No matching users found</p>
            <p class="text-sm">Try searching by name or email</p>
          </div>
        </:empty_state>
      </.autocomplete>
    </.form>
    \"""
  end

  def handle_event("search_users", %{"query" => query}, socket) do
    {:noreply, assign(socket, users: MyApp.Accounts.search(query))}
  end

  def handle_event("validate", _params, socket), do: {:noreply, socket}
end

Summary

Components

Renders an autocomplete component with type-to-search filtering and full keyboard navigation.

Components

autocomplete(assigns)

Renders an autocomplete component with type-to-search filtering and full keyboard navigation.

This component provides a combobox-style text input for search-driven selection. It supports client-side and server-side filtering, custom option rendering, grouped options, and rich empty-state handling. Use it whenever users need to find and select a single value from a potentially large list. It includes built-in form integration, error handling, and accessibility features following WAI-ARIA combobox patterns.

Attributes

  • id (:any) - Sets the unique identifier for the autocomplete component. When not provided, defaults to the name attribute value. Controls the IDs of the wrapper ({id}-autocomplete), text input ({id}), and listbox ({id}-listbox).

    Defaults to nil.

  • name (:any) - Specifies the form field name for the autocomplete. Required when not using the field attribute. This name is applied to the hidden input that submits the selected value.

  • field (Phoenix.HTML.FormField) - Binds the autocomplete 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 over name, value, and errors attributes.

  • class (:any) - Specifies additional CSS classes to apply to the listbox container. Use for custom widths, max-heights, or other layout adjustments to the floating dropdown.

    Defaults to nil.

  • label (:string) - Sets the primary label text displayed above the input. Renders as an HTML <label> element linked to the text input for accessibility. Also used as the aria-label on the combobox.

    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 input 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 input 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 placeholder text displayed in the text input when no value is entered. Guides the user on what to type to begin searching.

    Defaults to nil.

  • autofocus (:boolean) - Controls whether the input element receives focus automatically when the page loads. Use sparingly -- only for the primary action on a page.

    Defaults to false.

  • disabled (:boolean) - Controls whether the autocomplete is disabled. Disabled autocompletes cannot be interacted with, appear visually muted, and are excluded from form submission. The clear button is hidden when disabled.

    Defaults to false.

  • size (:string) - Controls the visual size of the autocomplete 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_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 of 0 means filtering starts immediately on the first character.

    Defaults to 0.

  • 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. This message is only shown when no :empty_state slot is provided.

    Defaults to "No results found for \"%{query}\".".

  • on_search (:string) - Specifies the LiveView event name to trigger for server-side searching. When set, the component dispatches a fluxon:autocomplete:search custom event that the hook translates into a pushEvent(on_search, %{"query" => query, "id" => id}) call. The server should update the options assign with the filtered results. Client-side filtering is bypassed when this is set.

    Defaults to nil.

  • debounce (:integer) - Controls the debounce time in milliseconds for server-side searches. Delays the on_search event to avoid excessive server calls while the user is typing. Only applies when on_search is set.

    Defaults to 200.

  • search_mode (:string) - Controls the client-side search matching strategy. Only applies when on_search is not set.

    • "contains" -- Matches if the option label includes the query anywhere (default)
    • "starts-with" -- Matches only if the option label starts with the query
    • "exact" -- Matches only if the option label equals the query exactly

    Defaults to "contains".

  • open_on_focus (:boolean) - Controls whether the listbox opens automatically when the input receives focus, even before any characters are typed. When false (default), the listbox only opens when the user starts typing. The search_threshold still applies -- the listbox only opens on focus if the current query length meets the threshold.

    Defaults to false.

  • value (:any) - Sets the currently selected value. For standalone usage, pass a string or atom. When using the field attribute, this is automatically extracted from the form data. The component resolves the matching option label to display in the text input.

  • errors (:list) - Specifies a list of error messages to display below the input. When present, the field wrapper receives a data-invalid attribute for error styling. Errors are automatically extracted when using the field attribute with form validation.

    Defaults to [].

  • options (:list) (required) - Specifies the list of options for the autocomplete. Supports multiple formats following the Phoenix.HTML.Form.options_for_select/2 API:

    • 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 optional disabled: true
    • Keyword pairs: [Admin: "admin", User: "user"]
    • Grouped: [{"Group Label", [nested_options]}] -- supports unlimited nesting depth
  • 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".

  • clearable (:boolean) - Controls whether selections can be cleared. When enabled, displays a clear button (X icon) inside the input that appears when a value is selected. Clicking it resets both the visible text input and the hidden form value. The clear button is hidden when the component is disabled.

    Defaults to false.

  • Global attributes are accepted. Specifies additional HTML attributes passed through to the hidden <input> element. Supports the form attribute to associate with a form by ID. Supports all globals plus: ["form"].

Slots

  • inner_prefix - Renders content inside the input field's border, before the text entry area. Ideal for icons or short textual prefixes that should appear within the input area. Icon sizing is automatically adjusted based on the autocomplete's size attribute.Accepts attributes:
    • class (:any) - Specifies CSS classes for the inner prefix container.
  • outer_prefix - Renders content outside and before the input field's border. Suitable for standalone labels, dropdown menus, or buttons that visually attach to the autocomplete's left edge. When containing a Fluxon.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 input field's border, after the text entry area. Suitable for status icons, validation indicators, or informational elements. Appears after the clear button when clearable is enabled.Accepts attributes:
    • class (:any) - Specifies CSS classes for the inner suffix container.
  • outer_suffix - Renders content outside and after the input field's border. Ideal for action buttons, submit controls, or links that visually attach to the autocomplete's right edge. When containing a Fluxon.Components.Button, the border is automatically hidden to avoid double borders.Accepts attributes:
    • class (:any) - Specifies CSS classes for the outer suffix container.
  • 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 use in-data-highlighted, in-data-selected, and in-data-disabled Tailwind variants for state-aware styling.Accepts attributes:
    • class (:any) - Specifies additional CSS classes for the option wrapper.
  • empty_state - Defines custom rendering for the empty state shown when no options match the search query. When provided, replaces the default no_results_text message entirely. Useful for rich empty states with icons, illustrations, or action buttons (e.g., "Create new item").Accepts attributes:
    • class (:any) - Specifies additional CSS classes for the empty state container.
  • header - Renders custom content at the top of the listbox, above the options list. Useful for filter buttons, category tabs, or informational banners.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.