# Pill (/docs/pill)





`Pill` is a small chip for a single value or token. It's what `MultiSelect` and `TagsInput` render each selected value into, and it works standalone anywhere you need a removable tag. Neutral by default; pass a `color` to tint it, and `withRemoveButton` to add a `×` control.

```tsx
import { Pill, PillGroup } from '@42-central/ui-react/pill';

<Pill>C</Pill>
<Pill withRemoveButton onRemove={() => remove('cpp')}>C++</Pill>
```

## Playground [#playground]

<story.Playground />

## Removable [#removable]

`withRemoveButton` renders a trailing `×` that fires `onRemove`. The button is keyboard-focusable and labelled (`removeLabel`, default `"Remove"`); its click is stopped from bubbling so it won't trigger an enclosing option or input. When both `testId` and `withRemoveButton` are set, the remove button gets its own `` `${testId}-remove` `` — see the [Testing](/docs/testing) guide.

<Preview code="`<Pill withRemoveButton onRemove={() => {}}>C++</Pill>`">
  <Pill>
    C++
  </Pill>
</Preview>

## Colors [#colors]

Without a `color` the pill is a neutral gray. A `color` tints it with that palette's **soft** surface and text (`--c-soft` / `--c-text`), so it reads as a quiet token, not a loud badge. See [Colors](/docs/colors).

<Preview align="block" code="&#x22;<Pill>neutral</Pill>\n<Pill color=\&#x22;brand\&#x22;>brand</Pill>\n<Pill color=\&#x22;green\&#x22;>green</Pill>\n<Pill color=\&#x22;purple\&#x22;>purple</Pill>&#x22;">
  <div className="flex flex-wrap items-center gap-2">
    <Pill>
      neutral
    </Pill>

    <Pill color="brand">
      brand
    </Pill>

    <Pill color="green">
      green
    </Pill>

    <Pill color="purple">
      purple
    </Pill>

    <Pill color="red">
      red
    </Pill>
  </div>
</Preview>

## Sizes [#sizes]

Five sizes — `xs` through `xl` — so pills can match the input they sit inside.

<Preview align="block" code="&#x22;<Pill size=\&#x22;xs\&#x22;>xs</Pill>\n<Pill size=\&#x22;sm\&#x22;>sm</Pill>\n<Pill size=\&#x22;md\&#x22;>md</Pill>\n<Pill size=\&#x22;lg\&#x22;>lg</Pill>\n<Pill size=\&#x22;xl\&#x22;>xl</Pill>&#x22;">
  <div className="flex flex-wrap items-center gap-2">
    <Pill size="xs">
      xs
    </Pill>

    <Pill size="sm">
      sm
    </Pill>

    <Pill size="md">
      md
    </Pill>

    <Pill size="lg">
      lg
    </Pill>

    <Pill size="xl">
      xl
    </Pill>
  </div>
</Preview>

## Pill.Group [#pillgroup]

`PillGroup` lays pills out in a wrapping row and sets `size` (and `disabled`) once for every child — each pill can still override. This is the shape `MultiSelect` / `TagsInput` use to render their values.

<Preview align="block" code="&#x22;<PillGroup size=\&#x22;md\&#x22;>\n  <Pill withRemoveButton>C</Pill>\n  <Pill withRemoveButton>C++</Pill>\n  <Pill withRemoveButton>Unix</Pill>\n  <Pill withRemoveButton>Docker</Pill>\n</PillGroup>&#x22;">
  <PillGroup size="md">
    <Pill>
      C
    </Pill>

    <Pill>
      C++
    </Pill>

    <Pill>
      Unix
    </Pill>

    <Pill>
      Docker
    </Pill>
  </PillGroup>
</Preview>

## Disabled [#disabled]

`disabled` dims the pill and disables its remove button. Set it on a single pill or on the whole `PillGroup`.

<Preview code="&#x22;<PillGroup disabled>\n  <Pill withRemoveButton>Locked</Pill>\n  <Pill withRemoveButton>Read-only</Pill>\n</PillGroup>&#x22;">
  <PillGroup>
    <Pill>
      Locked
    </Pill>

    <Pill>
      Read-only
    </Pill>
  </PillGroup>
</Preview>

## API [#api]

### Pill [#pill]

<TypeTable
  type="{
  children: {
    type: 'ReactNode',
    description: 'Pill content. Long values truncate with an ellipsis.',
  },
  size: {
    type: '&#x22;xs&#x22; | &#x22;sm&#x22; | &#x22;md&#x22; | &#x22;lg&#x22; | &#x22;xl&#x22;',
    default: '&#x22;md&#x22;',
    description: 'Chip height, padding, and font. Inherited from PillGroup if unset.',
  },
  color: {
    type: 'Color',
    description: 'Tints the chip with the palette soft surface. Neutral when unset.',
  },
  withRemoveButton: {
    type: 'boolean',
    default: 'false',
    description: 'Render a trailing × button.',
  },
  onRemove: {
    type: '() => void',
    description: 'Called when the remove button is activated.',
  },
  removeLabel: {
    type: 'string',
    default: '&#x22;Remove&#x22;',
    description: 'Accessible label for the remove button.',
  },
  disabled: {
    type: 'boolean',
    default: 'false',
    description: 'Dim the pill and disable its remove button. Inherited from PillGroup.',
  },
  classNames: {
    type: '{ root?, label?, remove? }',
    description: 'Per-part class overrides.',
  },
  testId: {
    type: 'string',
    description: 'data-testid on the root element. When withRemoveButton is set, the remove button additionally gets `${testId}-remove`. See the Testing guide.',
  },
}"
/>

### PillGroup [#pillgroup-1]

<TypeTable
  type="{
  size: {
    type: '&#x22;xs&#x22; | &#x22;sm&#x22; | &#x22;md&#x22; | &#x22;lg&#x22; | &#x22;xl&#x22;',
    default: '&#x22;md&#x22;',
    description: 'Shared size for child pills (each pill can override).',
  },
  disabled: {
    type: 'boolean',
    default: 'false',
    description: 'Disable every pill in the group.',
  },
}"
/>

`Pill` forwards remaining props to its root `span` (incl. `asChild`); `PillGroup` forwards to its root `div`.

## Accessibility [#accessibility]

* The remove button is a real `<button>` with an `aria-label` (`removeLabel`) so assistive tech announces "Remove". It's keyboard-focusable and triggered by Enter / Space.
* The button's pointer interaction is stopped from bubbling and preserves focus on a host input — so removing a value inside a `PillsInput` / `MultiSelect` doesn't blur the field or submit an option.
* RTL: the chip is a plain flex row and the remove button uses logical margin (`-me-0.5`), so it mirrors correctly under `dir="rtl"`.
