<!-- Protocore Design System — Chip -->
# Chip

- **Category:** Inputs (`inputs`)
- **Slug:** `inputs/chip`
- **Status:** stable
- **Platforms:** web, mobile
- **Import:** `import { Chip } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/inputs/chip

> An interactive filter toggle — mono uppercase ghost outline that inverts to the primary fill when selected.

## Chip vs Badge vs Tag vs Button

These four look adjacent but encode different things — the case-and-family contract keeps them distinct:

- **Chip** — *interactive*. Mono UPPERCASE, a ghost outline that inverts when selected, `aria-pressed`. Use it to **filter or toggle** a view.
- **Badge** — *static status*. Sentence-case sans on a tone tint, never clickable. Use it to **show state** (Live, Degraded, Pending).
- **Tag** — *static metadata*. As-typed mono, muted, no fill. Use it to **label** (a commit hash, a version, a key).
- **Button** — *one-shot action*. It doesn't hold a selected state; it *does* something when pressed.

Rule of thumb: if clicking it changes what's **shown**, it's a Chip; if it just *is* information, it's a Badge or Tag; if it *runs* something, it's a Button.

## Mobile (React Native)

**Preview.** `@protocore/pds-mobile` ships the React Native sibling of **Chip**. It mirrors the web API where React Native allows; the package is a **preview** with no device-level QA yet, so pin it and expect small changes.

Import it from the mobile package (not `@protocore/pds`), inside a `<PdsProvider>` — there is no stylesheet, so `style` (a `ViewStyle`) replaces `className` and every value comes from the theme:

```tsx
import { Chip } from "@protocore/pds-mobile";
```

**Parity with web.** An interactive filter toggle built on `Pressable` (no `:hover`).

- Selected inverts to the primary fill on press, matching web; state is controllable via `selected` / `defaultSelected` / `onSelectedChange`.
- Pass `onDismiss` to render a trailing × affordance (labelled by `dismissLabel`).
- a11y: `accessibilityState` `{ selected, disabled }`.

```tsx
<Chip defaultSelected onSelectedChange={setActive}>Errors</Chip>
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | no | `false` | Render as the single child element (Radix `Slot`) instead of a `<button>`. The × affordance is unavailable in this mode. |
| `className` | `string` | no | — | — |
| `defaultSelected` | `boolean` | no | — | Initial selected state when uncontrolled. |
| `dismissLabel` | `string` | no | `Remove` | Accessible label for the × affordance. Defaults to `"Remove"`. |
| `onDismiss` | `(() => void)` | no | — | When provided, renders a trailing × affordance; called (without toggling) when it is activated. |
| `onSelectedChange` | `((selected: boolean) => void)` | no | — | Fires with the next selected state whenever the chip is toggled. |
| `selected` | `boolean` | no | — | Controlled selected state. Pair with `onSelectedChange`. |
| `style` | `CSSProperties` | no | — | — |

## Examples

### Basics

A Chip is a **toggle**. Use `defaultSelected` for uncontrolled state, or pair `selected` with `onSelectedChange` to control it. It exposes `aria-pressed`, so assistive tech announces it as a pressed button.

```tsx
import { Chip } from "@protocore/pds";

export default function Basics() {
  // Uncontrolled toggle — click to flip between the ghost outline and the
  // inverted primary fill.
  return <Chip defaultSelected>Validators</Chip>;
}
```

### A filter set

The canonical use: a row of multi-select filters over a dataset. Each Chip owns its own selected state; the parent tracks which are active.

```tsx
import { Chip } from "@protocore/pds";
import { useState } from "react";

const REGIONS = ["eu-central", "us-east", "ap-south", "sa-east"];

export default function FilterSet() {
  const [active, setActive] = useState<string[]>(["eu-central"]);

  const toggle = (region: string) =>
    setActive((prev) =>
      prev.includes(region) ? prev.filter((r) => r !== region) : [...prev, region],
    );

  return (
    <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
      {REGIONS.map((region) => (
        <Chip
          key={region}
          selected={active.includes(region)}
          onSelectedChange={() => toggle(region)}
        >
          {region}
        </Chip>
      ))}
    </div>
  );
}
```

## Do & don't

**Do**

- Use Chips for multi- or single-select filters over a list or grid.
- Keep labels to one or two mono words — "eu-central", "tier:1".
- Control the set from the parent when the selection drives a query.
- Add onDismiss to let people clear an applied filter in place.

**Don't**

- Don't use a Chip to show read-only status — that's a Badge.
- Don't use a Chip to trigger navigation or a one-shot action — that's a Link or Button.
- Don't rely on colour alone; the inverted fill plus aria-pressed carries the state.
- Don't mix Chips and Badges in the same row — the interactivity signal gets muddy.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Tab / Shift+Tab` | Move focus onto / off the chip (and onto its × when present). |
| `Space or Enter` | Toggle the chip's selected state. |
| `Enter / Space (on ×)` | Dismiss the chip without toggling it. |

**Notes**

- Renders a `<button>` with `aria-pressed` reflecting the selected state.
- The dismiss × is a separate focusable control with its own `dismissLabel` (default "Remove").
- Controlled via `selected` + `onSelectedChange`; uncontrolled via `defaultSelected`.
- `asChild` is available, but the × affordance is not injected in that mode.

## Related

`badge`, `tag`, `segmented-control`, `filter-bar`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
