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.
Installation
pnpm dlx shadcn add @eased-pro/reorder-listnpx shadcn add @eased-pro/reorder-listyarn dlx shadcn add @eased-pro/reorder-listbunx --bun shadcn add @eased-pro/reorder-listNeeds the @eased-pro registry and your Pro token in components.json. Create a token and copy the setup from your account .
Install the dependencies
pnpm add motionnpm install motionyarn add motionbun add motionAdd the Eased pieces it builds on
Copy each item’s files the same way, or install them with the CLI:
Add the files
- components/eased/reorder-list/reorder-list.tsx
Source is included with Eased Pro. Members download the files with the CLI command above, or through the MCP server.
Keep the folder layout
The files import each other by relative path (
../lib/motion), so keep each one at the path shown, undercomponents/eased/.
Usage
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-pressedwhile 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 components
- Sortable Data Table: order by a column instead of by hand.
- Animated Group-By List: rows that regroup.
- Custom Scrollbar (Scroll Area)