Reorder
Reorder is a drag-to-reorder list component built on Gesture.Pan() and
the FLIP layout engine: drag an item, drop it in a new spot, and every other
item animates into place. It handles list order, gap-closing, and the
settle animation for you, so you don't have to wire drag position, index
math, and FLIP transitions by hand.
Pieces
const Reorder = {
Group: ReorderGroup,
Item: ReorderItem,
Handle: ReorderHandle,
Context: ReorderContextProvider,
};
Reorder.Groupowns the list: the array of values and the axis it reorders along.Reorder.Itemwraps a single row/card. It's what's actually draggable.Reorder.Handle(optional) restricts dragging to a smaller area inside an item, like a grip icon, instead of the whole row.Reorder.Context(optional) wraps multipleReorder.Groups so items can be dragged between them, not just reordered within one.
Basic usage
import { useState } from 'react';
import { Reorder } from 'react-ui-animate';
function List() {
const [items, setItems] = useState(['Alpha', 'Bravo', 'Charlie', 'Delta']);
return (
<Reorder.Group values={items} onReorder={setItems}>
{items.map((item) => (
<Reorder.Item key={item} value={item} style={{ padding: 12 }}>
{item}
</Reorder.Item>
))}
</Reorder.Group>
);
}
onReorder fires with the reordered array while dragging, so state stays in
sync live rather than only on drop. value is what identifies each item
(it's used for both the key and drag tracking), so use something stable
like an id, not the array index.
Signature
interface ReorderGroupProps<T> {
values: T[];
onReorder: (values: T[]) => void;
axis?: 'x' | 'y'; // default: 'y'
transition?: FlipOptions; // settle animation for displaced items
style?: CSSProperties;
className?: string;
}
interface ReorderItemProps<T> {
value: T;
style?: CSSProperties;
className?: string;
}
interface ReorderHandleProps {
style?: CSSProperties;
className?: string;
}
Drag handles
By default the whole item is draggable. Wrap just the part that should
initiate dragging in Reorder.Handle to leave the rest of the row free for
clicks, text selection, or other controls:
<Reorder.Item value={item}>
<Reorder.Handle style={{ cursor: 'grab' }}>⠿</Reorder.Handle>
<span>{item.label}</span>
<button onClick={() => remove(item)}>Delete</button>
</Reorder.Item>
Horizontal lists
<Reorder.Group values={items} onReorder={setItems} axis="x" style={{ display: 'flex' }}>
{items.map((item) => (
<Reorder.Item key={item} value={item}>{item}</Reorder.Item>
))}
</Reorder.Group>
Dragging between lists
Wrap sibling Reorder.Groups in Reorder.Context to allow dragging an item
from one group into another, such as moving a card between Kanban columns:
import { Reorder } from 'react-ui-animate';
function Board() {
const [todo, setTodo] = useState(['Write docs']);
const [done, setDone] = useState(['Ship feature']);
return (
<Reorder.Context>
<Reorder.Group values={todo} onReorder={setTodo}>
{todo.map((t) => <Reorder.Item key={t} value={t}>{t}</Reorder.Item>)}
</Reorder.Group>
<Reorder.Group values={done} onReorder={setDone}>
{done.map((t) => <Reorder.Item key={t} value={t}>{t}</Reorder.Item>)}
</Reorder.Group>
</Reorder.Context>
);
}
Each group keeps its own state and onReorder callback; Reorder.Context
only tracks which group an item is currently hovering over so it can hand
the item off between them on drop.
Custom settle transition
transition controls the animation displaced items use to settle into
their new spot. It accepts the same shape as flipOptions on animate.*
components (a spring or timing descriptor, options-only, see flip
transitions):
import { Reorder, withSpring } from 'react-ui-animate';
<Reorder.Group
values={items}
onReorder={setItems}
transition={withSpring({ stiffness: 500, damping: 35 })}
/>
Best practices
Do
- Use a stable identity (an id, not the index) for
value. - Reach for
Reorder.Handleon rows that contain other interactive elements (buttons, links, inputs), so dragging doesn't swallow clicks on them.
Don't
- Don't mutate
itemsin place insideonReorder. Treat the callback's argument as the new array and set it directly, the same as any other list state update.
Next steps
- useGesture: the
Gesture.Pan()primitiveReorderis built on, for reorder-like interactions this component doesn't cover. - FlipGroup: the lower-level FLIP layout engine that powers the settle animation.