List Sortable

  • 1Motion Sickness · Phoebe Bridgers3:50
  • 2Nobody · Mitski3:13
  • 3Pink + White · Frank Ocean3:04
  • 4Seventeen · Sharon Van Etten4:31
  • 5Cellophane · FKA twigs3:24
  • 6Holocene · Bon Iver5:36

Press Space or Enter to pick up a row. Use the arrow keys to move it, Space or Enter to drop it, and Escape to cancel.

About this component

List Sortable

A list you reorder by dragging: the row lifts off the page and follows the pointer, the other rows step aside to make room, the list scrolls on its own near a scroll edge, and on drop every row settles into its new place from where it was drawn. On touch a short long press lifts the row, so the list still scrolls under a passing finger. The keyboard does it all too: Space picks a row up, the arrows carry it, Space drops it and Escape puts it back, with every step announced to screen readers. Drag the whole row, or only its grip so the row can hold buttons and links. Controlled or uncontrolled, any item shape, your own row content.

Usage

The import and the props worth knowing about, in one place.

import { ListSortable } from "@/components/beste/component/list-sortable";

// Controlled, with your own row content
<ListSortable
  items={tracks}
  onReorder={(next) => setTracks(next)}
  getItemLabel={(track) => track.title}
  renderItem={(track, { index }) => (
    <span className="flex flex-1 items-center justify-between">
      <span>{index + 1}. {track.title}</span>
      <span className="text-muted-foreground">{track.length}</span>
    </span>
  )}
/>

// Rows that hold buttons: only the grip drags
<ListSortable
  items={tasks}
  onReorder={setTasks}
  handle
  renderItem={(task) => (
    <>
      <span className="flex-1">{task.name}</span>
      <button onClick={() => console.log("remove", task.id)}>Remove</button>
    </>
  )}
/>

// Plain strings, uncontrolled
<ListSortable defaultItems={["Vocals", "Guitar", "Drums"]} getKey={(item) => item} />

Keyboard and gestures

Every one of these is in the component already. They are listed because a props table cannot mention a gesture, so nothing else on this page can tell you they exist.

DragLift a row and move it. The other rows make room, and the list scrolls near its edges.
Long press (touch)Lift a row. A finger that moves first scrolls the list instead.
Space / EnterPick the focused row up, or drop it where it is.
Arrow keysCarry the picked up row one place up or down.
Home / EndCarry it to the top or the bottom.
EscapePut the row back where it started. Works mid drag too.

Props

Read from the component's own type, so this cannot drift from what it accepts.

PropTypeDefaultDescription
itemsT[]—The rows, in order. Pair with `onReorder` to control them.
defaultItemsT[]—The starting rows when uncontrolled.
onReorder(items: T[], move: { from: number; to: number; item: T }) => void—Called with the reordered list when a row is dropped somewhere new.
getKey(item: T, index: number) => string—A stable key per row. Defaults to `item.id`; index keys break the settle animation.
getItemLabel(item: T) => string—What screen readers call a row. Defaults to `label`, `title` or `name`.
renderItem(item: T, state: SortableItemState) => React.ReactNode—The row's content. The grip and the surface are drawn around it.
handleboolean—Only the grip starts a drag, so the rest of the row can hold links and buttons.
gripbooleantrueDraw the grip at the start of each row. With `handle`, the grip is always drawn.
tone"muted" | "outline" | "ghost""outline"
size"sm" | "default" | "lg""default"
disabledboolean—
aria-labelstring"Sortable list"
classNamestring—
itemClassNamestring—

More List components

View all List

Ideas

3
  • LisbonNov 2Coliseu dos RecreiosAsk Arooj Aftab to open
  • OsloNov 9Sentralen
  • KyotoDec 1Taku TakuAcoustic set only

Booked

2
  • ReykjavíkOct 12HarpaSupport: Ólafur Arnalds
  • BerlinOct 18Funkhaus

On the road

3 / 3
  • LondonOct 3BarbicanStrings with Hania Rani
  • ParisOct 5La Cigale
  • BrusselsOct 7Ancienne Belgique

Done

1
  • GlasgowSep 28BarrowlandSold out, record the encore

Press Space or Enter to pick up a card. Use the up and down arrows to move it within a column, left and right to move it between columns, Space or Enter to drop it, and Escape to cancel.