---
title: Reorder
description: Drag list rows by a handle to reorder them.
---
import Source from './_generated/reorder.mdx';

Drag list rows into a new order by their grip handle. Pressing the handle lifts the row, its siblings slide to preview the drop slot, and the list auto-scrolls when you drag near its edges. On release the component emits `{ from, to }` — you apply the move to your own array (the "auto" completion model, contrast Ionic's manual `complete()`) and let it re-render. Dragging starts **only** from the handle, so vertical list scrolling stays natural.

<DemoFrame component="reorder" height={460} />

> **Info**
>
> Reorder is a pointer gesture — try a mouse drag on the grip handle on desktop, or drag on a touch device. There is also a keyboard path: focus a handle, press <kbd>Space</kbd> to pick the row up, <kbd>↑</kbd>/<kbd>↓</kbd> to move it, and <kbd>Space</kbd> to drop (<kbd>Esc</kbd> cancels).

## Installation

```bash
npx touchcn add reorder
```

## Usage

<CodeGroup>

```html Angular
<tcn-reorder (reorder)="onReorder($event)">
  @for (item of items(); track item) {
    <tcn-reorder-item>
      <div class="row">{{ item }}</div>
      <tcn-reorder-handle />
    </tcn-reorder-item>
  }
</tcn-reorder>
```

```tsx React
import { TcnReorder, TcnReorderItem, TcnReorderHandle } from '@/components/ui/reorder';

<TcnReorder onReorder={({ from, to }) => setItems((items) => move(items, from, to))}>
  {items.map((item) => (
    <TcnReorderItem key={item}>
      <div className="row">{item}</div>
      <TcnReorderHandle />
    </TcnReorderItem>
  ))}
</TcnReorder>
```

```vue Vue
<script setup lang="ts">
import { TcnReorder, TcnReorderItem, TcnReorderHandle } from '@/components/ui/reorder';
</script>

<template>
  <TcnReorder @reorder="({ from, to }) => (items = move(items, from, to))">
    <TcnReorderItem v-for="item in items" :key="item">
      <div class="row">{{ item }}</div>
      <TcnReorderHandle />
    </TcnReorderItem>
  </TcnReorder>
</template>
```

</CodeGroup>

Apply the emitted move to your own data, for example:

```ts
function move<T>(items: T[], from: number, to: number): T[] {
  const next = [...items];
  const [moved] = next.splice(from, 1);
  next.splice(to, 0, moved);
  return next;
}
```

All gesture behaviour — pointer tracking, the `translateY` writes, hit-testing, sibling shifting, auto-scroll and the keyboard reorder — lives in the engine (`TcnReorderDirective` / `useReorder`); the copied components own only markup. Long-press-anywhere to drag is out of scope (dragging is handle-only by design); a future engine addition can layer it on without changing this markup.

## Accessibility

Each handle is a focusable `role="button"`. The keyboard reorder follows the WAI listbox-reorder pattern at a **baseline** depth: <kbd>Space</kbd>/<kbd>Enter</kbd> toggles pick-up (reflected via `aria-pressed`), the arrow keys move the grabbed row one slot at a time and focus follows it, and <kbd>Esc</kbd> cancels. Screen-reader announcements are limited to the handle's `aria-pressed` state — richer live-region narration is not yet implemented.

## Props

### Reorder

| Prop                    | Type                                | Default | Description                                            |
| ----------------------- | ----------------------------------- | ------- | ------------------------------------------------------ |
| `reorder` / `onReorder` | `(e: { from, to }) => void`         | —       | Fired on commit; apply the move to your own array.     |
| `disabled`              | `boolean`                           | `false` | Disable both the drag and keyboard reorder.            |

### Reorder Handle

| Prop    | Type     | Default             | Description                          |
| ------- | -------- | ------------------- | ------------------------------------ |
| `label` | `string` | `'Drag to reorder'` | Accessible label for the grip.       |

<Source />
