Skip to main content

Reorder

Drag-to-reorder lists with layout animations built in.

Reorder.Group and Reorder.Item turn any list into a drag-to-reorder list. Items are draggable, animate to their new positions, and the group reports the new order.

  • ⇅ spring
  • ⇅ tween
  • ⇅ inertia
  • ⇅ keyframes
		<script>
	import { Reorder } from "motion-start";
 
	let items = $state(["spring", "tween", "inertia", "keyframes"]);
</script>
 
<Reorder.Group values={items} onReorder={(next) => (items = next)}>
	{#snippet children({ item })}
		<Reorder.Item value={item}>
			{item}
		</Reorder.Item>
	{/snippet}
</Reorder.Group>
	

Reorder.Group

Prop Type Description
values T[] The array being reordered. Required.
onReorder (newOrder: T[]) => void Called with the new order as items are dragged. Required.
axis "x" | "y" Drag axis. Defaults to "y".
as string Element to render. Defaults to "ul".

The group renders its children through a snippet that receives each item, so the list stays keyed by value.

Reorder.Item

Prop Type Description
value T The item's entry in values. Required.
as string Element to render. Defaults to "li".

Reorder.Item is a motion component, so whileDrag, transition and the other animation props all work.

		<Reorder.Item value={item} whileDrag={{ scale: 1.04 }} transition={{ type: "spring" }}>
	{item}
</Reorder.Item>
	

Moving between lists

Each column is a Reorder.Group, which owns the order inside that list. Add the same layoutId to each Reorder.Item and it can also be re-parented between groups: Reorder handles the source column while layoutId carries the card's identity across the handoff.

Scheming 4

  • Acquire the last glowing rock
  • Blot out the sun (Q3 stretch goal)
  • Rehearse the monologue
  • Rebrand "world domination" as "synergy"

In motion 3

  • Assemble the doomsday device
  • Bribe a small European parliament
  • Interview henchmen (round four)

Conquered 2

  • Buy a dormant volcano
  • Betray my business partner

Give every group the same stable cards array, then conditionally render each card in its committed or preview column. Cards reorder while they remain in their source column. Once the pointer crosses into another column, layoutId hands the active drag to that group's copy and opens its target slot; release commits the preview.

		<Reorder.Group
	values={cards}
	onReorder={(next) => {
		if (previewColumn) return;
		reindex(next);
	}}
>
	{#snippet children({ item: card })}
		{#if renderColumn(card) === column}
			<Reorder.Item
				value={card}
				layoutId={card.id}
				drag
				dragConstraints={{ top: 0, left: 0, right: 0, bottom: 0 }}
				dragElastic={1}
				onDrag={(event, info) => previewDrop(card, info)}
				onDragEnd={(event, info) => commitDrop(card, info)}
				whileDrag={{ scale: 1.04, rotate: -1.5, zIndex: 10 }}
			>
				{card.title}
			</Reorder.Item>
		{/if}
	{/snippet}
</Reorder.Group>