Skip to content

Pill

Shows a removable token for a record or a selected value, alone or in a list.

Press delete or backspace to remove

Installation

pnpm dlx shadcn@latest add @wisty/pill

The CLI installs the Radix or Base UI file to match your project, with what it builds on: button.

If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.

When to use

Use it

  • A field shows records or values someone picked, like the accounts in a lookup or the options of a multi-select. Put them in a PillGroup.
  • A page shows a related record as a compact token that links to it, like an account on a filter bar.
  • Someone needs to remove a value they added, one at a time.

Don't use it

  • For a status or a count. Use Badge. A badge describes something, and a pill stands for a thing people can remove.
  • For a filter someone switches on and off. Use Toggle or Toggle group.
  • For the chips inside a Combobox with multiple. Use its ComboboxChip, which works with the combobox's own keyboard handling.
  • For a long list of selections. Show a count and a list instead, because a group of pills wraps onto many lines.

Usage

The label is the pill's children.

import { Icon } from "@/components/wisty/icon"
import { Pill } from "@/components/wisty/pill"
<Pill
  href="/accounts/001"
  icon={<Icon name="standard:account" label="Account" />}
>
  Acme Corporation
</Pill>

Selected values go in a PillGroup, as the Selected records example shows. Pass onRemove to add the remove control, and remove the value from your state in it.

Examples

Linked

Pills outside a group whose labels link with `href`: plain, with an account icon and with an avatar in `icon`, each with a remove button.

States

An `invalid` pill with an error icon, and a long account name truncated by its parent.

API reference

PartBuilt on
Pill<span> (opens in a new tab)
PillGroup<div> (opens in a new tab)

Pill

PropTypeDefaultDescription
hrefstringMakes the label a link.
iconReact.ReactNodeAn Icon or Avatar before the label.
onRemove() => voidShows a remove control, and is called when it's used.
removeLabelstring"Remove"The remove button's accessible name.
invalidbooleanfalseMarks the pill as an error.

PillGroup

PropTypeDefaultDescription
aria-label*stringNames the group for screen readers, like "Selected accounts".

SLDS mapping

Pill is slds-pill with a slds-pill__label. With href it's slds-pill_link, and the link, slds-pill__action, draws the border. icon is slds-pill__icon_container. The remove control is slds-pill__remove: a slds-button_icon outside a group, a slds-icon_container inside one. PillGroup is slds-pill_container around slds-listbox slds-listbox_horizontal. The remove glyph is Lucide's X where SLDS draws utility:close.

Known gaps:

  • A linked pill with invalid keeps its gray border, since the link draws it. The label still turns red. A plain pill's border does turn red.
  • SLDS's bare pill (slds-pill_bare) and its collapsed selection group with a "more" toggle aren't covered.
PropValueSLDSNotes
href(set) (added)slds-pill slds-pill_link, slds-pill__actionThe label is a link, and the link draws the pill's border.
href(unset) (added)slds-pill
icon(set) (added)slds-pill__icon_containerAn Icon or Avatar, shown at 16px.
onRemove(set) (added)slds-pill__removeA remove button. In a PillGroup, a remove icon that isn't focusable.
invalidtrue (added)slds-has-error
PillGroup(part) (added)slds-pill_container, slds-listbox slds-listbox_horizontalA listbox of pill options.

Keyboard interactions

In a PillGroup, which is one tab stop on the last pill that had focus:

Key Action
Right arrow Moves to the next pill, from the last back to the first.
Left arrow Moves to the previous pill, from the first to the last.
Backspace, Delete Removes the focused pill, if it has onRemove, and focuses the next one, or the previous one if it was last.

Accessibility

  • PillGroup needs an aria-label that says what's selected, like "Selected accounts".
  • Outside a group, the remove button is named by removeLabel. Pass "Remove Acme Corporation" rather than the default "Remove" when there are several pills.
  • In a group, the remove icon is hidden from screen readers and takes no focus. Keyboard users remove pills with Backspace or Delete, which the group's hidden description tells them.
  • invalid changes the color only. Add an error icon with a label, as the States example does, so screen readers hear the problem.
  • A string label is repeated in a title, so a cut-off label can be read on hover. A label made of elements gets no title.

Responsive behavior

  • A pill is never wider than its parent. A long label is cut off with an ellipsis.
  • A PillGroup wraps its pills onto more lines as they fill it.

Rules

  • Give each pill in a PillGroup a key. Without it, removing any pill but the last drops focus to the page.
  • Use href only outside a group. An option in a listbox can't be a link.
  • Don't move focus yourself after a removal. The group already has.
  • Never pass asChild or render.