Skip to content

Sortable component

Sortable puts the items of a list, row or grid in a new order. A mouse or pen drags an item by its handle, a finger holds and then drags, and the keyboard picks an item up, moves it with the arrow keys and drops it, with every step announced. You keep the items; onReorder gives you the new order. To move things between areas use Draggable and DropZone, and between Kanban columns use Board.

Layout & Display11 propsAccessibility notes

Playground

Change a prop on the right and the preview updates. Switch to Code to copy what you see.

A reorderable list

Give items, a getKey that returns a unique stable key, and renderItem. getLabel is the name read aloud when an item is picked up and moved. onReorder is called once per drop with the items in their new order and { key, from, to }.

const [tasks, setTasks] = useState(initialTasks);

<Sortable
  aria-label="Tasks"
  items={tasks}
  getKey={(t) => t.id}
  getLabel={(t) => t.title}
  renderItem={(t) => <Card>{t.title}</Card>}
  onReorder={(next) => setTasks(next)}
/>

Grids and dragging from anywhere

layout is vertical, horizontal or grid (set --oks-sortable-columns to change the columns). dragFromItem lets a pointer drag from anywhere on an item instead of only the handle; inputs, buttons and links inside still work as usual.

<Sortable
  aria-label="Gallery order"
  layout="grid"
  dragFromItem
  items={images}
  getKey={(img) => img.id}
  getLabel={(img) => img.alt}
  renderItem={(img, { isDragging }) => <img src={img.src} alt={img.alt} data-faded={isDragging} />}
  onReorder={(next, { from, to }) => save(next, from, to)}
/>

Props

The full public API for Sortable. Every prop is stable and intentionally typed.

Data

items

T[]

default: —

The items, in their current order.

getKey

(item: T) => string

default: —

A unique, stable key for an item.

onReorder

(items: T[], move: { key, from, to }) => void

default: —

Called once per drop with the items in their new order and the 0-based old and new positions.

Content

getLabel

(item: T) => string

default: the key

The item's name, read when it is picked up and moved.

renderItem

(item: T, state) => ReactNode

default: —

Draws an item. state has index, isDragging (pointer) and isLifted (keyboard).

labels

Partial<SortableLabels>

default: English

handle(name), hint, pickedUp, movedTo, dropped, cancelled. Also set app-wide with LocaleProvider.

aria-label

string

default: —

Names the list.

Appearance

layout

"vertical" | "horizontal" | "grid"

default: vertical

How the items flow. horizontal wraps; grid uses --oks-sortable-columns.

classNames

Partial<Record<slot, string>>

default: —

Class per part: base, item, handle, content.

Behavior & states

dragFromItem

boolean

default: false

A pointer can drag from anywhere on an item, not only its handle. Inputs, buttons and links inside still work.

isDisabled

boolean

default: false

Nothing can be moved.

Accessibility

  • A list of list items. Each handle is a button named Move and the item name, with a hidden hint linked through aria-describedby and aria-pressed while the item is picked up.
  • On a handle: Space or Enter picks up, the arrow keys move, Home and End go to the ends, Space or Enter drops and Escape cancels and puts the item back. Left and right swap in right-to-left pages.
  • Every pick up, move, drop and cancel is announced through a status region, for example Dropped Task 2 at position 4 of 6.
  • A pointer drag starts after 5px of movement. A finger holds for 400ms first so the page still scrolls, or drags at once from the handle. Escape cancels a pointer drag.

Best practices

  • Keep getKey stable. Using the array index breaks the announcements and the order after a drop.
  • Give getLabel a short human name, not an id.
  • Save the new order in onReorder; Sortable does not keep your items.
  • Translate the announcements with the labels prop or LocaleProvider.