Pills Input

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJS-moduleApache-2.0
Installation mix mishka.ui.gen.headless pills_input
Source View Source

Overview of the Phoenix and LiveView Headless Pills Input Component

The Mishka Chelekom Headless Pills Input is a Phoenix function component for LiveView: an input-shaped container that holds pills followed by a text field. It is the base for tag and multi-select inputs. The root is a div, the pills slot comes first, and a type="text" field with autocomplete off comes after it. A click anywhere in the box focuses the field through Phoenix.LiveView.JS.focus, so there is no hook and no script to load.

The component does not add or remove pills. You render them in the slot, usually with the Headless Pill component, and keep the list on the server. The field takes input_name and input_value to bind to a form, and on_add to react to the Enter key. Style the box with class and the field with input_class. data-disabled is set on the root when the control is disabled.

Base UI Pills Input example - pills and a text field in one control (Phoenix headless)

Base UI Pills Input example - pills and a text field in one control (Phoenix headless)

ui phoenix
heex
<.pills_input
  id="baseui-pills-input"
  placeholder="Add a tag"
  class="flex w-full max-w-80 flex-wrap items-center gap-1.5 rounded-lg border border-neutral-300 bg-white px-2 py-1.5 text-sm cursor-text focus-within:ring-2 focus-within:ring-neutral-400 dark:border-neutral-700 dark:bg-neutral-900"
  input_class="min-w-24 flex-1 border-0 bg-transparent p-0.5 outline-none"
>
  <:pills>
    <.pill class="inline-flex items-center rounded-full bg-neutral-100 px-2 py-0.5 dark:bg-neutral-800">ui</.pill>
    <.pill class="inline-flex items-center rounded-full bg-neutral-100 px-2 py-0.5 dark:bg-neutral-800">phoenix</.pill>
  </:pills>
</.pills_input>

Attributes and events

id is required. The text field's id is the component id plus -input, so a label with for pointing at it names the field. input_name and input_value bind the field to a form, placeholder sets its placeholder, and disabled disables the field, turns off the click-to-focus behavior and sets data-disabled on the root.

on_add is bound as phx-keydown on the field and limited to the Enter key. Give it an event name or a JS command. The component does not read the text or create a pill. Your handler does that and keeps the list.

Adding and removing pills

Render one pill per item in the pills slot and keep the items in an assign. A form is the simplest way to add one: Enter submits it, phx-submit runs your handler, and the field clears when the component renders again with an empty input_value. Removal belongs to the pill: with_remove draws the button, remove_label names it, and on_remove takes an event name or a JS command.

heex
<.form for={@form} id="tags-form" phx-submit="add">
  <.pills_input
    id="tags"
    input_name={@form[:tag].name}
    input_value={@form[:tag].value}
    placeholder="Add a tag"
    class="flex flex-wrap items-center gap-1.5 rounded-lg border border-neutral-300 px-2 py-1.5"
  >
    <:pills>
      <.pill
        :for={tag <- @tags}
        with_remove
        remove_label={"Remove #{tag}"}
        on_remove={JS.push("remove", value: %{tag: tag})}
        class="inline-flex items-center gap-1 rounded-full bg-neutral-100 py-0.5 pr-1 pl-2"
      >
        {tag}
      </.pill>
    </:pills>
  </.pills_input>
</.form>

Parts & data attributes

Each part carries a stable data-part and a structural chelekom-pills-input* class you can target.

Partdata-partState / ARIAPurpose
Rootrootdata-disabledThe input-shaped container. Clicking it focuses the field.
Pills-your own markupWhatever the pills slot renders, drawn before the field.
Inputinputtype="text", autocomplete="off", disabledThe trailing text field.

Accessibility

There is no formal ARIA pattern for this control. The field is a native text input, so it keeps the browser's keyboard and screen reader behavior. Tab moves through the remove buttons and the field in document order, and Enter runs on_add. The field has no label of its own, so name it with a <label for> that points at <id>-input. Give each pill's remove button a specific remove_label such as "Remove phoenix", so a screen reader announces which pill it removes.