Pill
v0.0.10
mix mishka.ui.gen.headless pill
Overview of the Phoenix and LiveView Headless Pill Component
The Mishka Chelekom Headless Pill is a compact tag or token. It renders a span that holds your content and, when you set with_remove, a trailing button that removes it. It has no JavaScript and no styling. Use it to show values a person has already chosen, such as the tags on a post or the selections in a multi-select.
The remove button carries an accessible aria-label and calls whatever you give on_remove, so in a Phoenix LiveView it can be a plain event name or a Phoenix.LiveView.JS command. Each part takes its own class attribute: class for the root, then label_class and remove_class.
Base UI Pill example - read-only and removable pills (Phoenix headless)
Base UI Pill example - read-only and removable pills (Phoenix headless)
<div class="flex flex-wrap items-center gap-2">
<.pill
id="baseui-pill-design"
class="inline-flex items-center gap-1 rounded-full bg-neutral-100 px-2.5 py-0.5 text-sm text-neutral-800 dark:bg-neutral-800 dark:text-neutral-200"
>
Design
</.pill>
<.pill
id="baseui-pill-eng"
with_remove
remove_label="Remove Engineering"
class="inline-flex items-center gap-1 rounded-full bg-neutral-100 py-0.5 pr-1 pl-2.5 text-sm text-neutral-800 dark:bg-neutral-800 dark:text-neutral-200"
remove_class="inline-flex size-4 items-center justify-center rounded-full text-neutral-500 hover:bg-neutral-200 dark:hover:bg-neutral-700"
>
Engineering
</.pill>
<.pill
id="baseui-pill-product"
with_remove
remove_label="Remove Product"
class="inline-flex items-center gap-1 rounded-full bg-neutral-100 py-0.5 pr-1 pl-2.5 text-sm text-neutral-800 dark:bg-neutral-800 dark:text-neutral-200"
remove_class="inline-flex size-4 items-center justify-center rounded-full text-neutral-500 hover:bg-neutral-200 dark:hover:bg-neutral-700"
>
Product
</.pill>
</div>Removing pills and posting the list
on_remove is forwarded to the remove button as phx-click. Give it an event name, or a JS command. The pill does not know which item it stands for, so use JS.push with a value to say which one. Put a hidden input inside the pill and the list posts as an array with the form: removing a pill removes its input too.
<.form for={@form} phx-submit="save">
<div class="flex flex-wrap items-center gap-1.5">
<.pill
:for={tag <- @labels}
id={"label-#{tag}"}
with_remove
remove_label={"Remove #{tag}"}
on_remove={JS.push("remove", value: %{tag: tag})}
class="..."
remove_class="..."
>
{tag}
<input type="hidden" name="labels[]" value={tag} />
</.pill>
</div>
</.form>def handle_event("remove", %{"tag" => tag}, socket) do
{:noreply, assign(socket, :labels, List.delete(socket.assigns.labels, tag))}
endremove_label is the button's aria-label and defaults to "Remove". Make it specific, such as "Remove Engineering", so the buttons can be told apart in a list. disabled sets data-disabled on the root and disables the remove button.
Parts & data attributes
Each part carries a stable data-part and a chelekom-pill__* class you can target.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Root | root | data-disabled | The span that holds the pill. |
| Label | label | - | A span around the content. |
| Remove | remove | aria-label, disabled | A button with type="button", rendered only when with_remove is set. It shows a multiplication sign that is hidden from assistive technology. |
Accessibility
A pill is presentational, and there is no WAI-ARIA pattern for it. The remove control is a native button, so Enter and Space activate it, and its aria-label tells screen reader users what it removes. The multiplication sign inside it is hidden with aria-hidden. Keep each remove_label unique within a list.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | No | Optional unique id |
with_remove
|
boolean
|
false
| — | No | Render a trailing remove button |
on_remove
|
any
| — | — | No | phx-click event or JS command for the remove button |
remove_label
|
string
|
Remove
| — | No | aria-label for the remove button |
disabled
|
boolean
|
false
| — | No | Disable the pill (also sets data-disabled) |
class
|
any
| — | — | No | Extra classes for the root |
label_class
|
any
| — | — | No | Extra classes for the label part |
remove_class
|
any
| — | — | No | Extra classes for the remove button |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
inner_block
| Yes | The pill content |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
pill
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless pill