Prima.Combobox (Prima v0.3.0)
View SourceA searchable dropdown component with keyboard navigation and intelligent positioning.
The combobox combines an input field with a dropdown menu, supporting both frontend filtering and server-side async search. It uses Floating UI for intelligent positioning with automatic flipping and repositioning when scrolling or resizing.
Features
- Keyboard Navigation - Arrow key navigation and Enter to select
- Smart Positioning - Powered by Floating UI with automatic flipping and repositioning
- Dual Modes - Frontend filtering or server-side async search
- Create New Items - Optional "create new" functionality for user-generated content
- Multi-select - Select more than 1 option from the options list (WIP)
- Accessibility - ARIA roles, focus management, and keyboard support (WIP)
- Form Integration - Can be used like any other Phoenix form field (WIP)
Quick Start
Basic combobox with predefined options and frontend filtering:
<.combobox id="my-combobox" name="selection">
<.combobox_input placeholder="Search options..." />
<.combobox_options id="my-combobox-options">
<.combobox_option value="apple">Apple</.combobox_option>
<.combobox_option value="banana">Banana</.combobox_option>
<.combobox_option value="orange">Orange</.combobox_option>
</.combobox_options>
</.combobox>Advanced Usage
Server-Side Search (Async Mode)
For large datasets or server-side filtering, add on_search to the search input.
The component automatically switches to async mode when this attribute is present:
<.combobox id="users-combobox" name="user_id">
<.combobox_input
placeholder="Search users..."
on_search="search-users"
/>
<.combobox_options id="users-options" phx-update="replace">
<%= for user <- @search_results do %>
<.combobox_option value={user.id}><%= user.name %></.combobox_option>
<% end %>
</.combobox_options>
</.combobox>Smart Positioning with Floating UI
The options dropdown uses Floating UI for intelligent positioning:
<.combobox_options
id="my-options"
placement="top-start"
flip={true}
offset={10}
>
<!-- Options content -->
</.combobox_options>Create New Items
Allow users to create new items that don't exist in the options list:
<.combobox id="tags-combobox" name="tag">
<.combobox_input placeholder="Search or create tag..." />
<.combobox_options id="tag-options">
<%= for tag <- @tags do %>
<.combobox_option value={tag.name}><%= tag.name %></.combobox_option>
<% end %>
<.creatable_option class="italic text-gray-600" />
</.combobox_options>
</.combobox>Form Integration
Comboboxes are backed by a native <select> and follow its form-submission behavior.
Only selected option values are submitted, never the search text.
The name is used exactly as supplied. For Phoenix forms, use name="fruit" for a
single value and name="fruits[]" for a list of values in multiple mode.
When nothing is selected, the field is omitted from form data. Selecting an option
with value="" submits an empty string. Multiple mode does not submit an empty list
automatically.
A parent form's phx-change fires when selections are added or removed. Typing only
filters options or sends an on_search event with %{"query" => query}.
Summary
Functions
The main combobox container component.
The searchable input field for the combobox.
An individual selectable option within the combobox dropdown.
The dropdown container for combobox options.
Display label for a selected item within a combobox_selections template.
Remove button for multi-select combobox selections.
Container for displaying selected items in multi-select mode.
A special option that allows users to create new items.
Functions
The main combobox container component.
Wrap the input, options, and optional selections in this component.
Attributes
id(required) - Unique identifier for the comboboxname(required) - Submitted field name, used verbatim. Include[]for Phoenix list parameters in multiple mode (for example,name="roles[]").selections- A map with a required:valueand optional:display, or a list of these maps in multiple mode.class- Additional CSS classes to apply to the containermultiple- Enable multi-select mode (default:false)inner_block- Slot containing the input and options components. Use:letto receive the initial input text for server rendering.
Example
<.combobox id="my-combobox" class="w-full" phx-change="selection_changed" name="selection">
<.combobox_input />
<.combobox_options id="options">
<!-- Options content -->
</.combobox_options>
</.combobox>Attributes
id(:string) (required)name(:string) (required)selections(:any)class(:string) - Defaults to"".multiple(:boolean) - Defaults tofalse.- Global attributes are accepted.
Slots
inner_block(required)
The searchable input field for the combobox.
Filters options locally by default. Set on_search to an event name to search
on the server instead; no enclosing form is required. The handler receives
%{"query" => query} and should return default options when the query is empty.
Attributes
value- Initial input text, supplied by the root's:letwhen usingselections.class- CSS classes for the visible input fieldplaceholder- Placeholder text for the inputon_search- Event name for async search (enables async mode)phx-target- Optional LiveComponent target for the search eventsearch_debounce- Typing delay in milliseconds (default: 200; use 0 for no delay)
Examples
Initial selection:
<.combobox
id="user-combobox"
name="user_id"
selections={%{value: @user.id, display: @user.name}}
:let={input_value}
>
<.combobox_input value={input_value} />
<.combobox_options id="user-options">
<!-- Options content -->
</.combobox_options>
</.combobox>Without initial selections, omit both :let and the input's value.
Omitting selections preserves the current selection across server patches.
Use the list form inside a combobox with multiple={true}. Server patches apply
selections while the search input is unfocused. While it is focused,
the current selection and search text are preserved, like a native text input.
Blurring does not apply a skipped update; a subsequent patch can apply it.
Build selections from your form state and update it in your phx-change handler so
later patches retain the user's selection. Applying server values does not
fire a form change event.
Frontend filtering mode:
<.combobox_input
placeholder="Select category..."
class="w-full border rounded-md px-3 py-2"
/>Async search mode:
<.combobox_input
placeholder="Search users..."
on_search="search-users"
phx-target={@myself}
class="w-full border rounded-md px-3 py-2"
/>Attributes
class(:string) - Defaults to"".value(:string) - Defaults to"".on_search(:string) - Defaults tonil.search_debounce(:integer) - Defaults to200.- Global attributes are accepted. Supports all globals plus:
["placeholder", "phx-target"].
An individual selectable option within the combobox dropdown.
Each option represents a selectable item in the dropdown. Options support keyboard navigation, mouse hover, and click selection. In frontend mode, options are automatically filtered based on the search input.
Attributes
value(required) - The value submitted when this option is selecteddisplay- The display value shown in the search input (defaults tovalueif not provided)inner_block- The display content for the optionclass- CSS classes for styling the option
State Attributes
The component automatically adds HTML data attributes for styling:
data-focus- Set to"true"when the option is focused (keyboard/hover)data-selected- Set to"true"when the option is currently selected
Use these attributes to style options based on their state:
/* Style focused option */
.option[data-focus="true"] { background-color: #f3f4f6; }
/* Style selected option */
.option[data-selected="true"] { font-weight: 600; }Examples
Basic option:
<.combobox_option value="apple" class="px-3 py-2 hover:bg-gray-100">
🍎 Apple
</.combobox_option>With complex content and state styling:
<.combobox_option
value={user.id}
class="px-3 py-2 flex items-center data-focus:bg-indigo-600 data-selected:font-semibold"
>
<img src={user.avatar} class="w-6 h-6 rounded-full mr-2" />
<div>
<div class="font-medium"><%= user.name %></div>
<div class="text-sm text-gray-500"><%= user.email %></div>
</div>
</.combobox_option>Attributes
class(:string) - Defaults to"".value(:any) (required)display(:string) - Defaults tonil.- Global attributes are accepted.
Slots
inner_block(required)
The dropdown container for combobox options.
This component renders the dropdown that contains all selectable options. It uses Floating UI for intelligent positioning with automatic repositioning when scrolling, resizing, or when the dropdown would overflow the viewport.
Attributes
id(required) - Unique identifier for the options containerinner_block- Slot containing the option elementsclass- CSS classes for styling the dropdown container
Positioning Reference
reference- CSS selector for the element to position relative to (default: positions relative to the search input)
For multi-select comboboxes where the input shifts when selection pills are added/removed, you can specify a stable container element as the positioning reference using any valid CSS selector (e.g., "#my-field", ".field-wrapper", "[data-ref='field']").
Floating UI Positioning
placement- Dropdown position relative to reference. Options:top,top-start,top-end,right,right-start,right-end,bottom(default),bottom-start,bottom-end,left,left-start,left-endflip- Auto-flip to opposite side if no space (default:true)offset- Distance in pixels from the reference element (default: no offset)
CSS variables
--reference-width: Width of the search input, or the element selected byreference. Updates automatically while open.
Apply min-width: var(--reference-width) for a minimum width, or
width: var(--reference-width) for an exact match.
Transitions
transition_enter- Transition for showing the dropdowntransition_leave- Transition for hiding the dropdown
LiveView Integration
phx-update- LiveView update strategy for dynamic options (useful for async mode)
Examples
Basic options container:
<.combobox_options id="basic-options" class="bg-white border rounded-md shadow-lg">
<.combobox_option value="option1">Option 1</.combobox_option>
<.combobox_option value="option2">Option 2</.combobox_option>
</.combobox_options>With custom positioning:
<.combobox_options
id="positioned-options"
placement="top-end"
flip={false}
offset={5}
class="bg-white border rounded-md"
>
<!-- Options content -->
</.combobox_options>For async mode with transitions:
<.combobox_options
id="async-options"
phx-update="replace"
transition_enter={{"ease-out duration-100", "opacity-0 scale-95", "opacity-100 scale-100"}}
transition_leave={{"ease-in duration-75", "opacity-100 scale-100", "opacity-0 scale-95"}}
>
<%= for item <- @search_results do %>
<.combobox_option value={item.id}><%= item.name %></.combobox_option>
<% end %>
</.combobox_options>Attributes
class(:string) - Defaults to"".id(:string) (required)reference(:string) - Defaults tonil.placement(:string) - Defaults to"bottom-start". Must be one of"top","top-start","top-end","right","right-start","right-end","bottom","bottom-start","bottom-end","left","left-start", or"left-end".flip(:boolean) - Defaults totrue.offset(:integer) - Defaults tonil.transition_enter(:any) - Defaults tonil.transition_leave(:any) - Defaults tonil.- Global attributes are accepted. Supports all globals plus:
["phx-update"].
Slots
inner_block(required)
Display label for a selected item within a combobox_selections template.
JavaScript fills this span with the option's display text, falling back to its
value. Use surrounding markup for icons or other content that should be preserved.
Attributes
class(:string) - Defaults to"".- Global attributes are accepted.
Remove button for multi-select combobox selections.
Use this button inside a combobox_selections template. JavaScript binds it to
the selected item's submitted value and sets an aria-label using its display label.
A caller-provided aria-label is preserved.
Attributes
class- CSS classes for styling the buttoninner_block(required) - Button content (icon, text, etc.)
Example
<.combobox_selection_remove class="text-gray-500 hover:text-gray-700">
×
</.combobox_selection_remove>Attributes
class(:string) - Defaults to"".- Global attributes are accepted.
Slots
inner_block(required)
Container for displaying selected items in multi-select mode.
This component uses a template-based approach where JavaScript clones the selection
slot markup to create pills for each selected value. The container uses phx-update="ignore"
so LiveView doesn't interfere with client-side selection management.
Attributes
class- CSS classes for the selections containerselection- Required slot that defines the markup for each selected item. Usecombobox_selection_labelfor the display label andcombobox_selection_removefor a remove button. The surrounding markup can be fully customized.
Usage
<.combobox_selections class="flex flex-wrap gap-2">
<:selection class="inline-flex items-center gap-1 px-2 py-1 bg-blue-100 rounded">
<.combobox_selection_label />
<.combobox_selection_remove class="hover:bg-blue-200 rounded">
×
</.combobox_selection_remove>
</:selection>
</.combobox_selections>Attributes
class(:string) - Defaults to"".
Slots
selection(required) - Accepts attributes:class(:string)
A special option that allows users to create new items.
This component enables users to select values that don't exist in the predefined options list. It automatically shows/hides based on the search input and whether an exact match exists in the current options.
Behavior
- Auto-generated content: Displays "Create \"[search_term]\"" based on current input
- Smart visibility: Only shows when search input has content and no exact match exists
Attributes
class- CSS classes for styling the create option
Example
<.combobox id="tags-input" name="new_tag">
<.combobox_input placeholder="Search or create tag..." />
<.combobox_options id="tag-options">
<%= for tag <- @existing_tags do %>
<.combobox_option value={tag}><%= tag %></.combobox_option>
<% end %>
<.creatable_option class="px-3 py-2 italic text-blue-600 border-t" />
</.combobox_options>
</.combobox>When user types "new-tag" and it doesn't exist in options, this will show: "Create 'new-tag'" and submit "new-tag" as the value when selected.
Attributes
class(:string) - Defaults to"".