# Reorder List

> An animated reorderable list for React and shadcn, by drag or keyboard, with every move announced.

Drag to reorder with FLIP: neighbours slide aside, and on drop every row glides to its new place, the dropped one carrying the finger's velocity. Space lifts a row, arrows move it, Escape puts it back, and every step is announced.

- Tier: Eased Pro
- Interaction: Drag
- Page: https://eased.dev/components/reorder-list
- Installs: motion

## Install

Reorder List ships with Eased Pro (https://eased.dev/#pricing). Members add the `@eased-pro` registry with their token (https://eased.dev/account), then run:

```bash
npx shadcn add @eased-pro/reorder-list
```

Drag to reorder with FLIP, with a full keyboard mode and announcements. Built on the Eased gesture kit.

## When to use

- Priorities, playlists, checklists, form fields and navigation order.
- Short to medium lists (up to a few dozen rows) of any row height.

## When not to use

- Sorting by a column: use Sortable Table.
- Moving items between lists or into a grid: use a dedicated drag-and-drop library.

## Usage

```tsx example.tsx
import { ReorderList } from "@/components/eased/reorder-list/reorder-list";

export function Example() {
  const [tasks, setTasks] = useState(initialTasks);
  return (
    <ReorderList
      aria-label="Priorities"
      items={tasks}
      getKey={(t) => t.id}
      getLabel={(t) => t.title}
      onReorder={setTasks}
      className="flex flex-col gap-1"
      itemClassName="flex h-11 items-center gap-2 px-2"
      renderItem={(t, { handle }) => (
        <>
          {handle}
          <span className="truncate">{t.title}</span>
        </>
      )}
    />
  );
}
```

## API reference

### ReorderList

| Prop | Type | Default | Description |
|---|---|---|---|
| `items` | `T[]` | | The rows, in order. |
| `getKey` | `(item: T) => string` | | Stable key. |
| `getLabel` | `(item: T) => string` | | The name in announcements. |
| `onReorder` | `(items: T[]) => void` | | The new order after a drop or a keyboard move. |
| `renderItem` | `(item, { index, lifted, handle }) => ReactNode` | | Place `handle` (the grip) in the row. |
| `itemClassName` | `string` | `""` | Classes for each `li`; rows get `data-lifted` while lifted. |
| `aria-label` | `string` | | Names the list. |
| `className` | `string` | `""` | Classes for the `ul` (flex or grid with subgrid rows both work). |

## Keyboard

| Key | Action |
|---|---|
| `Tab` | Moves between grips. |
| `Space` / `Enter` | Lift the row; again to drop it. |
| `↑` / `↓` | Move the lifted row. |
| `Escape` | Cancel and put it back. |

## Accessibility

- Each grip is a button named "Reorder …", described by the instructions, `aria-pressed` while lifted.
- Every pick-up, move, drop and cancel is announced in an assertive live region ("Write release notes moved to position 3 of 5.").
- Mouse drags from anywhere on the row; on touch the row scrolls the page and the grip drags.

## Motion

While dragging, the row follows the pointer (rubber-banded past the list's ends), lifts to 1.015 with a floating shadow cross-faded on a pseudo-element, and its neighbours slide out of the way on the smooth spring. On drop the list commits and every row plays a FLIP from where it was drawn to where it now lives: the dropped row carries the pointer's velocity into a spring that overshoots once; the rest glide on the smooth spring. Keyboard moves use the same FLIP. Reduced motion: no lift scale, rows move at once.

## Notes for AI

Use it for any user-ordered list. Keep `items` in state and pass the setter as `onReorder`; give a human `getLabel` so announcements make sense. Place `state.handle` first in the row.

Do not add a drag library, `draggable`, or Motion's `layout` on the rows; positions are already animated with transforms. Do not reorder the array during a drag yourself.

## Related

- [Sortable Table](/components/sortable-table): order by a column instead of by hand.
- [Group-by List](/components/group-by-list): rows that regroup.
