# SortableList

A list the user reorders by dragging a row's handle or with the keyboard, such as dashboard widgets or a priority list. Each row has a grip handle; the rest of the row holds any content.

**Dependencies:** npm: 1 direct, 2 total; registry: 2 direct, 2 total.

## Usage

### When to use

- Let someone set their own order, such as the widgets on a dashboard or a list of priorities.
- Reorder a short list by dragging or with the keyboard, saving each new order.

### When not to use

- Order rows by a value such as a date or a name. Use Table with sortable columns.
- Show steps in a fixed order. Use Stepper.
- Pick several items from a list. Use Checkbox.

## Variants

### size

Row height and padding: sm rows are at least size-element-md tall for dense panels, md rows at least size-element-xl for content with two lines.

Values: `sm`, `md`. Default: `md`.

## Examples

### Dashboard widgets

```tsx
<SortableList label="Dashboard widgets" className="w-80">
  <SortableListItem value="revenue" label="Revenue" />
  <SortableListItem value="signups" label="Sign-ups" />
  <SortableListItem value="churn" label="Churn" />
  <SortableListItem value="traffic" label="Traffic" />
</SortableList>
```

### Rows with content

Children replace the label in the row; the label still names the handle.

```tsx
<SortableList label="Priorities" className="w-96">
  <SortableListItem value="billing" label="Fix billing emails">
    <span className="flex-1 truncate">Fix billing emails</span>
    <Badge variant="destructive">Urgent</Badge>
  </SortableListItem>
  <SortableListItem value="onboarding" label="Shorten onboarding">
    <span className="flex-1 truncate">Shorten onboarding</span>
    <Badge variant="info">Next</Badge>
  </SortableListItem>
  <SortableListItem value="export" label="CSV export">
    <span className="flex-1 truncate">CSV export</span>
    <Badge>Later</Badge>
  </SortableListItem>
</SortableList>
```

### Small, with a locked row

A disabled row keeps its place in the order but has no working handle.

```tsx
<SortableList label="Columns" size="sm" className="w-64">
  <SortableListItem value="name" label="Name" disabled />
  <SortableListItem value="email" label="Email" />
  <SortableListItem value="role" label="Role" />
  <SortableListItem value="joined" label="Joined" />
</SortableList>
```

## Do and don't

### Do

- Save the new order as soon as onValueChange fires, so a reload keeps it.
- Keep each row to one or two lines, so a moving row is easy to follow.
- Put links and buttons in the content; only the handle starts a drag.

### Don't

- Do not make rows reorderable when the order comes from a sort, such as by date. Use Table with sortable columns.
- Do not nest a SortableList inside another.
- Do not hide the handle; it is the only way to move a row on touch and with the keyboard.

## Props

| Name | Type | Values | Default | Description |
| --- | --- | --- | --- | --- |
| `label` (required) | `string` |  |  | On SortableList: names the list for screen readers, such as Dashboard widgets. On SortableListItem: the row's name in its handle and in the announcements; also the row's content when it has no children. |
| `value` | `string[] on SortableList, string on SortableListItem` |  |  | On SortableList: item values in their order, for a controlled list; items not named follow in written order. On SortableListItem: identifies the row in the order. Required on every item. |
| `defaultValue` | `string[]` |  |  | The first order, for an uncontrolled list. Without it the rows start in written order. |
| `onValueChange` | `(value: string[]) => void` |  |  | Called with the new order after a drop, and after each keyboard move. |
| `size` | `enum` | `sm`, `md` | `md` | Row height and padding: sm rows are at least size-element-md tall for dense panels, md rows at least size-element-xl for content with two lines. |
| `disabled` | `boolean` |  | `false` | Set on SortableList or SortableListItem. Turns the handles off; the content stays as it is. |
| `labels` | `{ handle?: string; instructions?: string; pickedUp?: string; moved?: string; dropped?: string; canceled?: string }` |  |  | Text for the handle name, the keyboard instructions and the announcements, with {label}, {position} and {total} filled in. Defaults: EN `Move {label}`, `Picked up {label}. Position {position} of {total}.` and the like. Pass Vietnamese text on a Vietnamese page. |

### Slots

| Name | Required | Description |
| --- | --- | --- |
| `content` | no | Children of SortableListItem: any content after the handle, such as an icon, a name and a Badge. |

## Anatomy

| Part | Description |
| --- | --- |
| `root` | The wrapper around the list and its live region. |
| `list` | The ul of rows. |
| `sortableItem` | One row: the handle, then the content. A surface with a border at rest; raised with a ring and a medium shadow while it moves. |
| `handle` | The 24px grip button at the start of the row. |
| `icon` | The grip icon inside the handle, 16px. |
| `hitArea` | The transparent ::before that grows the handle to size-target-min. |
| `content` | Any content after the handle; the label when there are no children. |

## Accessibility

Role: `list`

| Keys | Action |
| --- | --- |
| `Tab` | Moves to the next row's handle. |
| `Space / Enter` | Picks up the focused row, or drops a picked-up row where it is. |
| `ArrowUp / ArrowDown` | Moves a picked-up row one place. ArrowLeft and ArrowRight do the same. |
| `Home / End` | Moves a picked-up row to the start or the end. |
| `Escape` | Cancels the move and puts the row back where it was picked up. |

- A ul named by label. Each handle is a button named EN `Move {label}`, VI `Di chuyển {label}`, and described by the keyboard instructions.
- A picked-up handle has aria-pressed="true" and the row sets data-dragging.
- Every pick up, move, drop and cancel is announced in a live region with the row's new position, such as EN `Revenue moved to position 2 of 4.`
- Moving focus away from a picked-up handle drops the row where it is.
- Pointer and touch drag start on the handle only, so the content can hold links and buttons. The handle reaches size-target-min on touch, through a transparent ::before.

## Install

```bash
npx werewolf-ui add sortable-list
```

Run `npx werewolf-ui init` once in your app before adding components. Init sets up tokens, their CSS import and agent rules. Add installs this component and its dependencies while preserving your theme. See [Installation](https://www.werewolf-ui.dev/docs/installation.md) for requirements and the alternative registry setup.
