I've been exploring ways to create reusable UI components with Elixir and Phoenix LiveView. Here's my latest creation: a flexible, interactive tag input component that combines server-side rendering with client-side interactivity using LiveView Hooks.
What We're Building
The tag input component allows users to:
- Add items by typing and pressing Enter or Tab
- Remove items with a button or backspace (when enabled)
- Enforce a maximum item limit
- Detect and animate duplicate entries
- Provide autocomplete suggestions (optional)
- Submit items as part of a form
Here's a quick demo of what it looks like:
<FormUI.root for={@form} id="tag-form" phx-change="validate" phx-submit="save">
<FormUI.Tags.input
field={@form[:free_range]}
label="Anything Goes (List Input)"
description="This is a tag input field. You can add as many items as you want. Press `Enter` or `Tab` to add a new item. Press backspace or click on `x` to remove an item."
/>
<FormUI.Tags.input
field={@form[:limited_options]}
label="Limited Options (List Input, with options)"
description="This is a tag input field with limited options. You can only add items from the list. Press `Enter` or `Tab` to add a new item. Press backspace or click on `x` to remove an item. Press `ESC` to close the dropdown. Note: this uses popover Api and Anchor API. Soon I will add simple absolute position option, as well as a FloatingUI option. This searches for contains rather than starts_with"
options={@basic_options}
search_type="contains"
/>
<FormUI.Tags.input
field={@form[:languages]}
label="Languages (List Input, with options)"
description="An example language select input"
options={@language_options}
/>
<FormUI.actions>
<.button phx-disable-with="Saving...">Save</.button>
</FormUI.actions>
</FormUI.root>
The Architecture
This component is split into three main parts:
- Elixir Components: Phoenix components for server-side rendering and form integration
- JavaScript Hooks: LiveView Hooks for client-side interactivity
- CSS: TailwindCSS classes for styling (customizable)
Server-Side: Elixir Components
The Elixir side uses two main modules:
-
ListInputUI.Headless.List: Defines the core list structure with components likeroot,item,input, andoptionsthese component don't assume anything about your styles -
ListInputUI.Field: Provides a form-aware wrapper with labels, error handling and styling
Client-Side: LiveView Hooks
Two JavaScript hooks power the interactivity:
-
ListInputHook:- Manages the core list functionality
- Adds/removes items
- Enforces max items and duplicate detection
- Triggers change events for LiveView updates
-
ListInputOptionsHook:- Manages the autocomplete dropdown
- Filters options based on input
- Handles keyboard navigation (Arrow keys, Enter)
- Highlights matching text using CSS Highlights API
Integration with LiveView
The hooks integrate seamlessly with LiveView by:
- Attaching to DOM elements via
phx-hook - Dispatching
changeevents that trigger LiveView form updates - Using hidden inputs to submit data with forms
Next Steps
I plan to enhance this component by:
- Improving accessibility with ARIA attributes
- Show examples for searchable options in dropdown from the server
- Create a video of how i've done this.
You can find the full source code on @Gumroad, don't worry it's free if you choose so.
Top comments (0)