← Back to Examples

📦 Basic Usage

The essentials, each as ONE live picker you configure with control switches — selection & input mode, content & display, and groups — plus declarative markup, the scroll-to API, and filter-vs-navigate search.

🌐 client — runs in the browser (element JS API) 🖥️ server — LiveView round-trip

BU01 · Selection & Input Mode

One picker, many behaviors. Flip between multi- and single-select, add the inline clear (✕) and the count badge, change the search_input_mode (a readonly or hidden search gives keyboard-only navigation), toggle close_on_select, and switch text direction (LTR / RTL).

The wrapper emits phx-update="ignore" so morphdom stays out of the component's shadow-managed DOM — which means a control change here remounts the element with the new attributes instead of patching them in place, so the selection resets to the seeded value={~w(js ts)} on each toggle. (For runtime option/value changes without a remount, use push_update/3; see BU05b.)
search_input_mode:
dir:

BU02 · Content & Display

Icons and subtitles on the option rows, plus how the current selection is shown in the control: the badges_display_mode, where badges sit (badges_position), and the order the selected items appear in (selected_order).

badges_display_mode:
badges_position:
selected_order: selected badges reorder; the value stays as-picked
selected_order="custom" needs a comparator (selectedOrderCompareCallback), a JS-only callback — see the Custom Rendering page. Same for the group custom-label and ratio-counter callbacks in BU03.

BU03 · Groups

Group options under headers with a group member. Each group header shows a count of that group's selected items (in any mode — open the dropdown to see it). Turn on per-group select-all (group_select_mode="cascade") to also put a tristate checkbox on each header that toggles the whole group — the group name is never itself a value, so the selection carries member values only and a partially-selected group reads indeterminate. Turning on custom labels renders the header via renderGroupLabelContentCallback, whose 2nd arg carries the per-group selection — here it writes GROUP — N remaining (selectableCount − selectedCount).

group_select_mode:
getCountLabelCallback: drives the in-input counter and each group's count
renderGroupLabelContentCallback / getCountLabelCallback are JS function props (no attribute equivalent), installed here by a small Bu03Callbacks hook on (re)mount — see the Custom Rendering page for more callback examples.

BU04 · Declarative Usage (No JavaScript)

Pass inline <option>/<optgroup> children with data-icon · data-subtitle · selected

Use standard HTML <option> elements - no JavaScript needed for simple cases! The component reads value, selected, and data-* attributes from each option.

No JavaScript required - pure HTML!
Icons via data-icon attribute
Use <optgroup> for grouping
Use data-subtitle for descriptions
Using renderGroupLabelContentCallback to customize group headers

BU05 · Scroll-to API — scrollToGroup / scrollToValue / scrollToIndex

JS-only element methods (no wrapper attribute): el.scrollToGroup(name) · el.scrollToValue(value) · el.scrollToIndex(i) · el.clearSearch() — wired in the inline <script>

Imperative commands that bring an option or group into view. Pair with open() for an "open + jump" gesture (deep-linking to a member, revealing the active row on open). They work in floating, mobile fullscreen, virtual-scroll and tree modes and return false when the target isn't in the current filtered list. A companion clearSearch() reveals an option a search had filtered out so you can then scroll to it.

scrollToGroup centers the group header.
Virtual mode scrolls by index math — the row need not be rendered yet.
scrollTo* return values log here…
Show code
el.open();
el.scrollToGroup('backend');   // header centered (or the group's first row in virtual mode)
el.scrollToValue('redis');     // → false if filtered out by the current search
el.scrollToIndex(0);

// Reveal a filtered-out option, then scroll to it:
el.clearSearch();
el.scrollToValue('jp');

BU05b · Server-driven Scroll-to — push_command/3 (Elixir-only, no JS)

The SAME commands as BU05, but driven from the LiveView with Keenmate.WebMultiselect.push_command/3 — no inline <script>. The element only needs hook=true.

Every button below is a plain phx-click: the server receives it and replies with a push_command that opens/closes/scrolls the widget. The "auto-open on entry" widget uses ready_event= — when the picker signals it has finished building, the server opens it and jumps to a group, with zero user interaction. This is the reliable "land on the page with it already open + scrolled" pattern.

Each click round-trips to the server, which calls push_command/3.
Opened and scrolled to group "database" by the server when its ready event fired — no click. (Reset by reloading the page.) Pinned to mobile_presentation="floating" so the automatic open-on-entry stays a dismissible dropdown on phones instead of taking over the screen with the fullscreen sheet.
push_command/3 calls log here…

BU06 · Search Modes: Filter vs Navigate

search_mode="filter" hides non-matches · search_mode="navigate" jumps to matches while keeping all rows visible

Choose between filter mode (hide non-matches) or navigate mode (jump to matches while keeping all visible)

Hides non-matching options (standard behavior)
Keeps all options visible, jumps to matches (highlighted with left border)

When to use each mode:

  • Filter Mode: Large lists where you want to narrow down options (e.g., product catalogs, user lists)
  • Navigate Mode: Quick selection from known lists (e.g., country/state selectors)

Navigate-mode keyboard shortcuts:

  • type — first match becomes focused; all matches keep a left-border highlight
  • Ctrl/Cmd + ↓ — jump to next match
  • Ctrl/Cmd + ↑ — jump to previous match
  • ↑ / ↓ — move focus one option at a time
  • Home / End — jump to first / last option
  • PageUp / PageDown — move 10 options at a time
  • Enter — toggle selection of the focused option
  • Esc — clear the search (or close the dropdown if search is empty)
🔌 Server round-trip
0
The LiveView server saw 0 event(s) from the wrapper.
(pick an option anywhere on this page)