<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Reorderable

A list the user puts in order: recipe steps, a queue, favorites. Each row has a drag handle, a 44px target on a touch screen. Dragging it moves the row with the pointer, the other rows make room, and holding it near the top or bottom edge scrolls the list or the page. Let go and the row drops into the gap the others made for it, springing there from where it was, and it can be picked up again on the way. A touch that starts anywhere else on a row scrolls as usual.

```tsx
import { Reorderable } from "@/components/ouro/reorderable/reorderable";
```

## Example

```tsx
<Reorderable
  label="Steps"
  items={steps.map((step) => ({ id: step.id, label: step.title, content: <Item title={step.title} description={step.text} /> }))}
  onReorder={(ids) => setSteps(ids.map((id) => steps.find((step) => step.id === id)!))}
/>
```

## Usage

Dragging is never the only way. A tap or click on a handle, Enter, or a screen reader's activation opens a menu with Move up, Move down, Move to top and Move to bottom; the ones that cannot apply are disabled. From the keyboard, Space on a handle also picks the row up: the up and down arrow keys move it, Space drops it and Escape cancels, which puts it back. Moving focus away drops it where it is. Every move is announced to screen readers with the row's position ("Sear the ribs moved. Position 3 of 7."), and each spoken string and menu item is a prop with an English default.

The list shows the new order at once and keeps it; `onReorder` reports it, once per drop. With reduced motion the other rows jump out of the way instead of sliding, and a dropped row is in its place at once.

## Use instead when

- Use `ItemGroup` instead for a list whose order the user does not change, and `Resizable` to change sizes, not order.

## Props

Type: `ReorderableProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `canceledLabel` | `string` | `"{label} put back. Position {n} of {total}."` | Announced when a move is cancelled and the row goes back. |
| `className` | `string` |  | Layout only: margins, or a height with `overflow-auto` (`max-h-80 overflow-auto`) to scroll the list in place. Appearance classes warn in development. |
| `closeLabel` | `string` | `"Close"` | The item that closes a handle's menu on a phone, where it is a bottom sheet. |
| `disabled` | `boolean` |  | Stops reordering, for example while a save of the last move is in flight: no drag, no menu of moves, no pick-up with Space. The handles stay in the tab order, marked `aria-disabled`, so focus stays where it is and a screen reader says they are unavailable. A row held from the keyboard or dragged when it turns on is put back, and an open menu closes. |
| `droppedLabel` | `string` | `"{label} dropped. Position {n} of {total}."` | Announced when a row is dropped. |
| `handleLabel` | `string` | `"Reorder {label}"` | Each handle's accessible name; `{label}` is the row's label. |
| `heldLabel` | `string` | `"Picked up. Use the arrow keys to move, Space to drop, and Escape to cancel."` | Read after a handle's name, in place of the instructions, while its row is picked up from the keyboard. |
| `instructionsLabel` | `string` | `"Press Enter for a menu of moves. Press Space to pick up, then use the arrow keys to move, Space to drop, and Escape to cancel."` | How to use a handle from the keyboard, read after its name. |
| `items` (required) | `readonly ReorderableItem[]` |  | The rows, in order: `{ id, label, content }`. Pass them in a new order to change the order from outside. |
| `label` (required) | `string` |  | Names the list for screen readers, such as "Steps". It is always required. |
| `movedLabel` | `string` | `"{label} moved. Position {n} of {total}."` | Announced on each move, with the arrow keys or from a handle's menu. |
| `moveDownLabel` | `string` | `"Move down"` | The item of a handle's menu that moves the row down one place. |
| `moveToBottomLabel` | `string` | `"Move to bottom"` | The item of a handle's menu that moves the row to the end of the list. |
| `moveToTopLabel` | `string` | `"Move to top"` | The item of a handle's menu that moves the row to the start of the list. |
| `moveUpLabel` | `string` | `"Move up"` | The item of a handle's menu that moves the row up one place. |
| `onReorder` | `((ids: string[], move: ReorderableMove) => void)` |  | Called once when the user drops a row in a new place, or moves it from its handle's menu, with every id in the new order and the move. The list already shows the new order; save it, or keep it in state. |
| `pickedUpLabel` | `string` | `"{label} picked up. Position {n} of {total}."` | Announced when a row is picked up; `{label}`, `{n}` (its position) and `{total}` are filled in. |

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `axis`: Not a prop. Reorderable is a vertical list.
- `children`: Not a prop. Rows are data, with their content in `content`: `items={[{ id: "1", label: "Sear", content: <Item title="Sear" /> }]}`.
- `onChange`: Not a prop. Ouro UI uses `onReorder`: `onReorder={(ids) => save(ids)}`.
- `onDragEnd`: Not a prop. Ouro UI uses `onReorder`: `onReorder={(ids) => save(ids)}`.
- `renderItem`: Not a prop. Each row's `content` is what it shows: `items={[{ id: "1", label: "Sear", content: <Item title="Sear" /> }]}`.
- `values`: Not a prop. Ouro UI uses `items`: `items={steps.map((step) => ({ id: step.id, label: step.title }))}`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`).

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `ReorderableItem`

One row of a `Reorderable`.

```ts
interface ReorderableItem {
  /** Identifies the row; `onReorder` reports the new order as these ids. Unique in the list. */
  id: string;
  /** Names the row for its handle ("Reorder Sear the ribs") and in announcements. It is the row's content too, unless you pass `content`. */
  label: string;
  /** What the row shows, in place of the label: an `Item`, or any content. */
  content?: ReactNode;
}
```

### `ReorderableMove`

The move `onReorder` reports: the row's id and its position before and after, counting from 0.

```ts
interface ReorderableMove {
  id: string;
  from: number;
  to: number;
}
```
