Pill
Shows a removable token for a record or a selected value, alone or in a list.
Installation
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 itsComboboxChip, 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
| Part | Built on |
|---|---|
Pill | <span> (opens in a new tab) |
PillGroup | <div> (opens in a new tab) |
Pill
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | Makes the label a link. | |
icon | React.ReactNode | An Icon or Avatar before the label. | |
onRemove | () => void | Shows a remove control, and is called when it's used. | |
removeLabel | string | "Remove" | The remove button's accessible name. |
invalid | boolean | false | Marks the pill as an error. |
PillGroup
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label* | string | Names 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
invalidkeeps 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.
| Prop | Value | SLDS | Notes |
|---|---|---|---|
href | (set) (added) | slds-pill slds-pill_link, slds-pill__action | The label is a link, and the link draws the pill's border. |
href | (unset) (added) | slds-pill | |
icon | (set) (added) | slds-pill__icon_container | An Icon or Avatar, shown at 16px. |
onRemove | (set) (added) | slds-pill__remove | A remove button. In a PillGroup, a remove icon that isn't focusable. |
invalid | true (added) | slds-has-error | |
PillGroup | (part) (added) | slds-pill_container, slds-listbox slds-listbox_horizontal | A 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
PillGroupneeds anaria-labelthat 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.
invalidchanges the color only. Add an error icon with alabel, 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
PillGroupwraps its pills onto more lines as they fill it.
Rules
- Give each pill in a
PillGroupakey. Without it, removing any pill but the last drops focus to the page. - Use
hrefonly 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
asChildorrender.