Skip to main content

About Form Pill Group

Form Pill Group is an interactive component for managing collections of items that users can select, deselect, and remove. Use it for multi-select interfaces, tag management, or any scenario where users need to manage a list of discrete items.

Components

  • FormPillGroup - Container for form pills
  • FormPill - Individual interactive pill
  • useFormPillState - Hook for managing pill state

Installation

yarn add @twilio-paste/form-pill-group

Usage

import { FormPillGroup, FormPill, useFormPillState } from '@twilio-paste/form-pill-group';

const MyFormPills = () => {
  const pillState = useFormPillState();
  const [pills, setPills] = React.useState(['Option 1', 'Option 2', 'Option 3']);
  
  const handleRemove = (pillToRemove) => {
    setPills(pills.filter(pill => pill !== pillToRemove));
  };
  
  return (
    <FormPillGroup {...pillState} aria-label="Selected options:">
      {pills.map((pill) => (
        <FormPill
          key={pill}
          {...pillState}
          onDismiss={() => handleRemove(pill)}
        >
          {pill}
        </FormPill>
      ))}
    </FormPillGroup>
  );
};

Props

FormPillGroup

Also accepts state props from useFormPillState.

FormPill

Also accepts state props from useFormPillState.

State Management

Use useFormPillState to manage keyboard navigation and selection:
const pillState = useFormPillState();

<FormPillGroup {...pillState} aria-label="Items:">
  <FormPill {...pillState} onDismiss={handleRemove1}>Item 1</FormPill>
  <FormPill {...pillState} onDismiss={handleRemove2}>Item 2</FormPill>
</FormPillGroup>

Removable Pills

Pills with onDismiss show a close button:
const [items, setItems] = React.useState(['Tag 1', 'Tag 2', 'Tag 3']);
const pillState = useFormPillState();

const removeItem = (itemToRemove) => {
  setItems(items.filter(item => item !== itemToRemove));
};

<FormPillGroup {...pillState} aria-label="Tags:">
  {items.map(item => (
    <FormPill
      key={item}
      {...pillState}
      onDismiss={() => removeItem(item)}
    >
      {item}
    </FormPill>
  ))}
</FormPillGroup>

Selectable Pills

Pills can be selected/deselected:
const [selectedPills, setSelectedPills] = React.useState([]);
const pillState = useFormPillState();

const togglePill = (pill) => {
  if (selectedPills.includes(pill)) {
    setSelectedPills(selectedPills.filter(p => p !== pill));
  } else {
    setSelectedPills([...selectedPills, pill]);
  }
};

<FormPillGroup {...pillState} aria-label="Select options:">
  {options.map(option => (
    <FormPill
      key={option}
      {...pillState}
      selected={selectedPills.includes(option)}
      onClick={() => togglePill(option)}
    >
      {option}
    </FormPill>
  ))}
</FormPillGroup>

Size Variants

Default Size

<FormPillGroup {...pillState} aria-label="Items:" size="default">
  <FormPill {...pillState}>Item</FormPill>
</FormPillGroup>

Large Size

<FormPillGroup {...pillState} aria-label="Items:" size="large">
  <FormPill {...pillState}>Item</FormPill>
</FormPillGroup>

Variant Types

Listbox Variant (default)

Standard selectable pills:
<FormPillGroup {...pillState} aria-label="Items:" variant="listbox">
  {/* pills */}
</FormPillGroup>

Tree Variant

Allows pills to trigger other elements like dialogs:
<FormPillGroup {...pillState} aria-label="Items:" variant="tree">
  <FormPill {...pillState} onClick={openDialog}>Configure</FormPill>
</FormPillGroup>

With Icons

Add icons to pills:
import { UserIcon } from '@twilio-paste/icons/esm';

<FormPillGroup {...pillState} aria-label="Users:">
  <FormPill {...pillState}>
    <UserIcon decorative size="sizeIcon10" />
    John Doe
  </FormPill>
</FormPillGroup>

Keyboard Navigation

Form Pill Group supports keyboard interaction:
  • Arrow keys - Navigate between pills
  • Enter - Toggle pill selection
  • Delete/Backspace - Remove focused pill (if dismissible)
  • Tab - Move focus in/out of group

Accessibility

  • Uses role="listbox" or role="tree" for proper semantics
  • Each pill has role="option" or role="treeitem"
  • Keyboard navigation fully supported
  • Selected state announced to screen readers
  • Keyboard controls visually hidden but available to assistive tech
  • Requires aria-label for context

Common Use Cases

Tag Management

const [tags, setTags] = React.useState(['React', 'TypeScript']);

<FormPillGroup {...pillState} aria-label="Tags:">
  {tags.map(tag => (
    <FormPill
      key={tag}
      {...pillState}
      onDismiss={() => setTags(tags.filter(t => t !== tag))}
    >
      {tag}
    </FormPill>
  ))}
</FormPillGroup>

Multi-Select Filter

const [filters, setFilters] = React.useState(['Active', 'Premium']);

<FormPillGroup {...pillState} aria-label="Active filters:">
  {filters.map(filter => (
    <FormPill
      key={filter}
      {...pillState}
      selected
      onDismiss={() => removeFilter(filter)}
    >
      {filter}
    </FormPill>
  ))}
</FormPillGroup>

Recipient List

const [recipients, setRecipients] = React.useState(['[email protected]']);

<FormPillGroup {...pillState} aria-label="Recipients:">
  {recipients.map(email => (
    <FormPill
      key={email}
      {...pillState}
      onDismiss={() => removeRecipient(email)}
    >
      {email}
    </FormPill>
  ))}
</FormPillGroup>

Best Practices

Do

  • Provide clear aria-label for the group
  • Use for managing collections of items
  • Allow removal of items when appropriate
  • Limit visible pills (consider scrolling or “show more”)

Don’t

  • Don’t use for read-only data (use Display Pill Group)
  • Don’t show too many pills at once
  • Don’t forget keyboard controls
  • Don’t use for single selection (use Radio Group)

Build docs developers (and LLMs) love