Pills Input
v0.0.10
mix mishka.ui.gen.headless pills_input
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)
<.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.
<.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.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Root | root | data-disabled | The input-shaped container. Clicking it focuses the field. |
| Pills | - | your own markup | Whatever the pills slot renders, drawn before the field. |
| Input | input | type="text", autocomplete="off", disabled | The 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.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | Unique id; anchors the input for JS.focus |
input_name
|
string
| — | — | No | Name for the text input |
input_value
|
string
| — | — | No | Value for the text input |
placeholder
|
string
| — | — | No | Placeholder for the text input |
disabled
|
boolean
|
false
| — | No | Disable the control |
on_add
|
any
| — | — | No | phx-keydown handler fired on Enter |
class
|
any
| — | — | No | Extra classes for the control |
input_class
|
any
| — | — | No | Extra classes for the text input |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
pills
| No | The pills / tokens (e.g. the `pill` component) |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
pills_input
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless pills_input