DEV Community

Cover image for Building an Interactive Tag Input Component with Phoenix LiveView
Mykolas Mankevicius
Mykolas Mankevicius

Posted on • Edited on

Building an Interactive Tag Input Component with Phoenix LiveView

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>
Enter fullscreen mode Exit fullscreen mode

The Architecture

This component is split into three main parts:

  1. Elixir Components: Phoenix components for server-side rendering and form integration
  2. JavaScript Hooks: LiveView Hooks for client-side interactivity
  3. 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 like root, item, input, and options these 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:

  1. ListInputHook:

    • Manages the core list functionality
    • Adds/removes items
    • Enforces max items and duplicate detection
    • Triggers change events for LiveView updates
  2. 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 change events 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)