Skip to main content
Search
Search the documentation

Reorder list

Drag to reorder with a mouse, a finger or the keyboard - and persist once per drop.

pnpm dlx shadcn-svelte@latest add https://coral.imlargo.dev/r/kit-reorder-list.json

HTML5 draggable gets a sortable list working on a desktop in an afternoon, and then it does not work on a phone (touch devices do not fire its events), and there is no way to do it from the keyboard at all. The bound array usually updates on every row the pointer crosses, so an $effect that saves the order sends a request for each one.

  • Install
  • Lint
  • Test
  • Build
  • Deploy
Press Space to pick up. Use the arrow keys to move, Space to drop and Escape to cancel.

Saves: 0 · one per drag, not per row

What Coral adds

  • Pointer events, not drag-and-drop: mouse, pen and touch alike, with the page kept from scrolling under the finger.
  • A keyboard path: focus a handle, Space to pick up, arrows (and Home, End) to move, Space to drop, Escape to put it back. Tabbing away drops it where it is.
  • Announced. Picked up, each move, dropped and cancelled are spoken with the position ("moved to position 3 of 5"), and the handle describes how to use it. All wording is replaceable.
  • One write per drop: rows slide live while dragging, but items and onreorder only change on drop, and not at all for a drop back where it started.
  • Swaps where the eye expects. A row gives way when the dragged row's centre passes its centre, whichever part of the handle was grabbed. Neighbours slide aside, unless reduced motion is on.

Your own row

  • 1 Cut the release branch Amara
  • 2 Run the migration Wei
  • 3 Deploy to staging Sofia
  • 4 Promote to production Liam
Press Space to pick up. Use the arrow keys to move, Space to drop, Escape to cancel.

The item snippet replaces the row's body and keeps Coral's grip. The row snippet replaces everything: spread handle onto whatever should grab the row, whether that's a grip or the whole row.

Entries are keyed by getKey, which defaults to the entry itself. Pass it for objects that are recreated between renders, and never return the index, since the index is what changes.

Import

<script lang="ts">
	import ReorderList from '#lib/components/coral/kit/reorder-list/reorder-list.svelte';
</script>

Props

Everything a <ul> accepts stays available. On top of that:

PropTypeDefaultDescription
itemsT[]-Bindable. Written once per drop.
getKey(item: T) => unknownidentityIdentifies an entry across moves.
getLabel(item: T) => stringStringNames an entry for the handle and announcements.
onreorder(items: T[], { item, from, to }) => void-Once per drop that changed the order.
disabledbooleanfalseBlocks dragging.
handleLabel(label: string) => stringReorder …Accessible name of each handle.
instructionsstringEnglishDescribes the keyboard controls.
grabbed, moved, dropped, cancelled(label, position, total) => stringEnglishAnnouncements.
itemClassstring-Merged onto every row.
itemSnippet<[ItemContext<T>]>-Row body, after the grip.
rowSnippet<[ItemContext<T>]>-The whole row. Spread handle.

ItemContext is { item, index, dragging, handle }. Rows being dragged carry data-dragging.

Scope

Vertical lists only, and no auto-scroll while dragging near the edge of a scrolling container. Both are real needs, and neither has come up in a list short enough for this component to be the right tool. A board with columns is a block, not this.