Prima.Listbox (Prima v0.3.0)

View Source

A single-select listbox component for use as a form input.

Unlike Prima.Dropdown (an action menu, role="menu"), Listbox is a value picker (role="listbox") — selecting an option updates a hidden form field and the displayed value, similar to a native <select>.

Quick Start

<.listbox id="fruit-listbox" name="fruit" value={@selected_fruit}>
  <.listbox_trigger id="fruit-listbox-trigger">
    <.listbox_value>{@selected_fruit || "Select a fruit..."}</.listbox_value>
  </.listbox_trigger>

  <.listbox_options id="fruit-listbox-options" class="min-w-[var(--reference-width)]">
    <.listbox_option id="fruit-option-apple" value="apple">Apple</.listbox_option>
    <.listbox_option id="fruit-option-banana" value="banana">Banana</.listbox_option>
  </.listbox_options>
</.listbox>

Form Integration

Listbox renders a hidden <input> with the given name. Selecting an option updates the input's value and dispatches bubbling input and change events, so a parent form's phx-change fires exactly like it would for a native form field:

<form phx-change="form_changed">
  <.listbox id="fruit-listbox" name="fruit" value={@selected_fruit}>
    ...
  </.listbox>
</form>

def handle_event("form_changed", %{"fruit" => fruit}, socket) do
  {:noreply, assign(socket, selected_fruit: fruit)}
end

Displayed Value

The listbox value is rendered by the caller (so the initial page load is always correct — no flash of placeholder text), and updated instantly on the client when an option is picked, ahead of any server round-trip:

<.listbox_trigger id="fruit-listbox-trigger">
  <.listbox_value>{@selected_fruit || "Select a fruit..."}</.listbox_value>
</.listbox_trigger>

Disabling the Listbox

Pass disabled={true} to listbox to disable the entire field. Its hidden input is disabled in the server-rendered HTML, excluding the value from form submission while preserving it for when the field is enabled again.

On mount and LiveView updates, the JS hook applies native disabled to the trigger button, preventing interaction and removing it from the tab order. Disabling an open listbox closes it.

For styling, the listbox wrapper receives data-disabled in the server-rendered HTML. Style the trigger through that ancestor, e.g. with Tailwind's in-data-disabled: variant. Avoid styling the trigger with disabled: - the native attribute is only applied once the hook mounts, so the disabled styling would flicker in after the initial page load.

<.listbox id="fruit-listbox" name="fruit" value={@selected_fruit} disabled={@locked?}>
  <.listbox_trigger id="fruit-listbox-trigger" class="in-data-disabled:opacity-50">
    <.listbox_value>{@selected_fruit || "Select a fruit..."}</.listbox_value>
  </.listbox_trigger>
  <.listbox_options id="fruit-listbox-options" class="min-w-[var(--reference-width)]">
    <.listbox_option id="fruit-option-apple" value="apple">Apple</.listbox_option>
  </.listbox_options>
</.listbox>

Setting the Value from Client-Side Code

Prima.Listbox.JS.set_value/2 sets the value without a server round-trip. It updates the hidden input, the displayed value and the selection markers:

<button type="button" phx-click={Prima.Listbox.JS.set_value("fruit-listbox", "apple")}>
  Reset
</button>

Like setting .value on a native <select>, this dispatches no events by default, so a parent form's phx-change doesn't fire. Pass notify: true to also dispatch input and change, exactly like a user selection:

Prima.Listbox.JS.set_value("fruit-listbox", "apple", notify: true)

Without notify, the server never learns the new value. If this listbox re-renders (its value or slot content changes), LiveView restores the server's value. Re-renders elsewhere on the page don't affect it.

Disabled options can be set, since disabled only blocks user picks. A value that matches no option, including "", is ignored.

From plain JavaScript, dispatch the underlying event on the listbox element:

document.getElementById("fruit-listbox").dispatchEvent(
  new CustomEvent("prima:listbox:set-value", { detail: { value: "apple" } })
)

Summary

Functions

An individual selectable option within a listbox.

The listbox options.

The trigger button for a listbox.

The displayed value within a listbox trigger.

Functions

listbox(assigns)

Attributes

  • id (:string) (required)
  • name (:string) (required)
  • value (:any) - Defaults to nil.
  • disabled (:boolean) - Defaults to false.
  • Global attributes are accepted.

Slots

  • inner_block (required)

listbox_option(assigns)

An individual selectable option within a listbox.

Attributes

  • id (required) - Unique identifier, required for ARIA relationships
  • value (required) - The value submitted when this option is selected
  • display - The text shown in listbox_value when selected (defaults to value)
  • disabled - Boolean to mark the option as unselectable (default: false)

Attributes

  • id (:string) (required)
  • value (:any) (required)
  • display (:string) - Defaults to nil.
  • class (:string) - Defaults to "".
  • disabled (:boolean) - Defaults to false.
  • Global attributes are accepted.

Slots

  • inner_block (required)

listbox_options(assigns)

The listbox options.

CSS variables

  • --reference-width: Width of the trigger, or the element selected by reference. Updates automatically while open.

Apply min-width: var(--reference-width) for a minimum width, or width: var(--reference-width) for an exact match.

Attributes

  • id (:string) (required)
  • transition_enter (:any) - Defaults to nil.
  • transition_leave (:any) - Defaults to nil.
  • class (:string) - Defaults to "".
  • reference (:string) - Defaults to nil.
  • 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 to true.
  • offset (:integer) - Defaults to 4.
  • Global attributes are accepted.

Slots

  • inner_block (required)

listbox_trigger(assigns)

The trigger button for a listbox.

Render a listbox_value within the trigger so the JS hook can update the displayed value without changing other content such as icons.

Examples

<.listbox_trigger id="fruit-listbox-trigger">
  <.listbox_value>{@selected_fruit || "Select a fruit..."}</.listbox_value>
  <svg class="h-5 w-5" ...>...</svg>
</.listbox_trigger>

Accessible Naming

Labelling is up to the caller, as with a native <select>. The trigger's content is its accessible name (via aria-labelledby). If the trigger only shows the current value, put a visually hidden label next to it so that both the meaning and the value are announced:

<.listbox_trigger id="role-listbox-trigger">
  <span class="sr-only">Role:</span>
  <.listbox_value>{@selected_role}</.listbox_value>
</.listbox_trigger>

To disable the field, pass disabled={true} to the parent listbox. Its JS hook synchronizes this button's native disabled attribute. Style the disabled state of this button via an in-data-disabled: Tailwind variant.

Attributes

  • id (:string) (required)
  • class (:string) - Defaults to "".
  • Global attributes are accepted.

Slots

  • inner_block (required)

listbox_value(assigns)

The displayed value within a listbox trigger.

The JS hook updates this content when an option is selected without changing other trigger content.

Attributes

  • class (:string) - Defaults to "".
  • Global attributes are accepted.

Slots

  • inner_block (required)