← Back to Examples

🚀 New Flexible API

Flexible data handling, form integration, and powerful new features

1. Custom Object Structure

Use your own data structure! Specify which properties to use for value, display, and other fields using value_member and display_value_member.

2. [key, value] Tuple Arrays

Auto-detected! Simply pass an array of [key, value] tuples. Perfect for simple key-value pairs without creating objects.

3. Callbacks for Complex Logic

Use callbacks when you need complex logic to extract values. Callbacks take precedence over members.

getValueCallback, getDisplayValueCallback, getSubtitleCallback and getDisabledCallback can't be serialized as HTML attributes — they're assigned to the element by id in the inline script below.

4. Form Integration - JSON Format

Seamless HTML form integration! Hidden inputs are automatically created and updated. Choose from 3 formats: json, csv, or array.

5. Form Integration - CSV Format

Use CSV format for traditional comma-separated values. Great for legacy systems.

6. Form Integration - Array Format

Use array format to create multiple hidden inputs. Standard HTML array handling with name[].

Captured params

(submit the form to see captured params)

7. New Public API Methods

New properties and methods for easier value access:

  • getValue() - Returns single value or array depending on mode
  • selectedValue - Get the selected value(s)
  • selectedItem - Get the first selected item object
  • getSelected() - Get all selected item objects

8. Async Search / Lookup

Load options dynamically as users type. Perfect for large datasets, API searches, or real-time filtering.

searchCallback receives an AbortSignal as its 2nd argument. Forward it to fetch so a superseded keystroke cancels the in-flight request. Pair with search_debounce to also coalesce keystroke bursts. These callbacks are assigned to each element by id in the inline script below.

User Search (GitHub API with Fallback)

Product Search (Simulated API)

1500 ms

Crank the delay up, then type quickly — each keystroke fires a request and cancels the previous in-flight one (watch the log below).

Debounced Search (search-debounce)

300 ms

The API call fires only after you pause typing for the debounce window — so a fast burst of keystrokes collapses into a single request. Set it to 0 to fire on every keystroke.

Keystrokes: 0 API calls: 0

Country Lookup with Icons

💬 Badge Tooltips

Show informative tooltips when hovering over badges and remove buttons. Perfect for truncated text or additional context.

Basic Tooltips

Custom Tooltip Callback

Default content: display value + subtitle. Custom content: use getBadgeTooltipCallback (assigned in the inline script) for rich content. Remove-button tooltips show "Remove [item name]"; placement is configurable (top, bottom, left, right).

🎨 Custom Badge Display

Show different text in badges vs dropdown options. This example shows full details in dropdown ("John Doe - Senior Developer") with subtitle showing email, but just first names in badges ("John") for maximum space efficiency!

getDisplayValueCallback drives the dropdown text while getBadgeDisplayCallback drives the compact badge text — both assigned in the inline script. It falls back to the standard display value when getBadgeDisplayCallback isn't provided.

🎛️ Custom Action Buttons

Full control over dropdown action buttons! Add Select All, Clear All, and custom actions with dynamic visibility, tooltips, and custom handlers.

Built-in 'select-all' / 'clear-all' plus custom actions with onClick, dynamic visibility via getIsVisibleCallback, tooltips, and CSS classes. The actionButtons array is assigned in the inline script below.

🔗 Cascading Selects (Reactive Options)

Create dependent dropdowns that automatically update! Setting element.options = newArray triggers automatic re-rendering. Perfect for Organization → Business Unit → Department flows. This card mirrors upstream's pure-JS reactive cascade; a LiveView-driven variant lives in the extras section below.

Selected Path:
{
  "organization": null,
  "businessUnit": null,
  "department": null
}

✨ Benefits Summary

Flexible Data Handling

  • Works with any data structure
  • No need to transform your data
  • Auto-detects tuple arrays
  • Member or callback patterns
  • Full TypeScript support

Form Integration

  • Automatic hidden input creation
  • 3 format options (JSON, CSV, array)
  • Custom formatters via callback
  • Auto-updates on changes
  • Standard HTML form behavior

🧩 Wrapper-specific extras (not in upstream)

These examples go beyond upstream's example page — they show the same cascading and async-search features driven through Phoenix LiveView (server-side) instead of pure browser JavaScript.

🔗 Cascading selects (LiveView-driven)

Three coupled multiselects. Selecting an organization sends a change event through the LV hook, the server fetches the matching business units, then pushes the new options back to the dependent multiselect via push_event(socket, "web_multiselect:update", %{id, options, value}). Same flow from unit → department.

Why push_event instead of just re-rendering options={@business_units}? The wrapper auto-emits phx-update="ignore" on the element so morphdom doesn't tear down the component's internally-managed children. That ignore also blocks LV from morphing the data-options attribute on subsequent renders — so the canonical way to mutate option-list or selection state from the server is through the hook's handleEvent channel.
Selected: org = , unit =

🛜 Async search — LiveView tunneled (Elixir fetches GitHub)

Set search_event="github_search" on the wrapper. The hook installs a searchCallback that pushes %{"query" => q, "id" => id} to the LV; the server fetches GitHub, returns {:reply, %{results: [...]}, socket}, and the reply resolves the promise in the browser.

Why route through the server? (1) attach an auth token without leaking it to the client, (2) transform the response (filter, cache, enrich), (3) reach any backend API that doesn't allow CORS from the browser.