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

- **Category:** Data Display (`data-display`)
- **Slug:** `data-display/list-cell`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { ListCell } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/data-display/list-cell

> Composable list row: leading slot, title/subtitle, trailing controls, optional selection and whole-row link.

## When to use it

**ListCell** is the general-purpose row for settings lists, member directories, file browsers, and pickers — anywhere each item pairs an identity with controls or navigation. It composes with **Persona**, **Avatar**, **Badge**, and **IconButton** in its slots.

When the row is a flat, mono, column-aligned record (careers, changelogs, endpoint directories), use **ListingRow**. When you need sorting, a header, or pagination, graduate to **DataTable**.

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `className` | `string` | no | — | — |
| `defaultSelected` | `boolean` | no | `false` | Uncontrolled initial selected state. |
| `href` | `string` | no | — | When set, the whole row becomes a single link: a stretched anchor covers the surface (keyboard- and screen-reader-correct), while `trailing`/Checkbox stay clickable above it. Without it the row renders no pointer or hover affordance. |
| `leading` | `ReactNode` | no | — | Leading slot — an Avatar, Icon, thumbnail, or any mark. |
| `onSelectedChange` | `((selected: boolean) => void)` | no | — | Fires when the selection Checkbox toggles. |
| `selectable` | `boolean` | no | `false` | Show a leading selection Checkbox. |
| `selected` | `boolean` | no | — | Controlled selected state (pairs with `onSelectedChange`). |
| `selectLabel` | `string` | no | `Select item` | Accessible label for the selection Checkbox. |
| `size` | `enum` | no | `md` | Row density. |
| `style` | `CSSProperties` | no | — | — |
| `subtitle` | `ReactNode` | no | — | Optional muted second line. |
| `title` | `ReactNode` | yes | — | Primary line, rendered in ink. When `href` is set this becomes the row link. |
| `trailing` | `ReactNode` | no | — | Trailing slot — a control, badge, menu trigger, or meta. Stays clickable above the row link. |

## Examples

### Basics

A `leading` slot (avatar / icon / thumbnail), a `title` + optional `subtitle`, and a `trailing` slot for controls or meta. Rows share a hairline divider and stack straight into a list.

```tsx
import { ListCell, Avatar, Badge } from "@protocore/pds";

export default function ListCellBasics() {
  return (
    <div>
      <ListCell
        leading={<Avatar name="Ada Lovelace" />}
        title="Ada Lovelace"
        subtitle="ada@protocore.io"
        trailing={<Badge tone="success">Owner</Badge>}
      />
      <ListCell
        leading={<Avatar name="Alan Turing" />}
        title="Alan Turing"
        subtitle="alan@protocore.io"
        trailing={<Badge tone="neutral">Member</Badge>}
      />
      <ListCell
        leading={<Avatar name="Grace Hopper" />}
        title="Grace Hopper"
        subtitle="grace@protocore.io"
        trailing={<Badge tone="neutral">Member</Badge>}
      />
    </div>
  );
}
```

### Selectable

Set `selectable` to add a leading Checkbox. It's controllable via `selected` / `defaultSelected` / `onSelectedChange`, and a selected row tints with the accent-muted wash.

```tsx
import * as React from "react";
import { ListCell, Avatar } from "@protocore/pds";

const people = [
  { id: "ada", name: "Ada Lovelace", email: "ada@protocore.io" },
  { id: "alan", name: "Alan Turing", email: "alan@protocore.io" },
  { id: "grace", name: "Grace Hopper", email: "grace@protocore.io" },
];

export default function ListCellSelectable() {
  const [selected, setSelected] = React.useState<Record<string, boolean>>({ alan: true });

  return (
    <div>
      {people.map((p) => (
        <ListCell
          key={p.id}
          selectable
          selected={!!selected[p.id]}
          onSelectedChange={(next) => setSelected((s) => ({ ...s, [p.id]: next }))}
          selectLabel={`Select ${p.name}`}
          leading={<Avatar name={p.name} />}
          title={p.name}
          subtitle={p.email}
        />
      ))}
    </div>
  );
}
```

## Usage

**Do**

- Use `href` for navigating rows — it stretches one anchor over the surface.
- Keep trailing controls to a small, consistent set across the list.
- Pair `selectable` with a controlled `selected` for bulk-selection UIs.
- Compose a Persona into `leading`+`title`, or use the slots directly.

**Don't**

- Don't add a second link inside a row that already has an `href`.
- Don't attach an onClick to fake a link — use `href` for whole-row navigation.
- Don't overload the `trailing` slot; two controls is plenty.
- Don't rely on the selected tint alone — the Checkbox carries the state.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Tab` | Moves focus to the row link (when href) and to the Checkbox / trailing controls. |
| `Enter` | Follows the row link (native anchor). |
| `Space` | Toggles the selection Checkbox when focused. |

**Notes**

- The whole-row link is a single stretched anchor named by the `title` — no nested clickables.
- The selection Checkbox is labelled by `selectLabel` (default "Select item").
- Trailing controls and the Checkbox sit above the stretched link so they remain independently operable.
- ListCell imposes no list role — provide `role="listitem"` / `role="option"` from the parent context.

## Related

`persona`, `avatar-group`, `listing-row`, `checkbox`

---

© 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/
