Sortable
A vertical list whose items can be reordered. It reports a move and you own the array — so the same list can drive a form field, a draft, or an optimistic write.
Import
import {
Sortable,
SortableItem,
reorder,
} from "@mikenotthepope/substrateui"Composition
Sortable
└── SortableItemIt holds no order
Sortable never reorders anything. It tells you two indices and stops, which is what lets the array stay where it already lives — in form state, in a draft you save on submit, in a mutation you roll back. A component that owned the order would have to be told about every one of those.
reorder ships with it because every caller needs the same four lines. It returns a copy, and an out-of-range index returns the list unchanged rather than throwing — the drag reads an index off the DOM, so that case is reachable without a caller mistake.
The buttons are the control
Dragging a grip is a pointer gesture with no keyboard equivalent, so a list that can only be dragged fails WCAG 2.1.1 — and reordering is often the whole point of the screen. SortableItem therefore renders the move buttons itself and marks the grip aria-hidden. There is no prop to turn them off.
Each button is named after its item rather than its action, so a screen reader hears "Move Phone call up" instead of five identical "Move up"s. That is what label is for; give it the same text a sighted reader sees in the row.
One item
Both moves are at an end, so both buttons are disabled. Nothing special is needed at the call site — the list handles its own ends.
Accessibility
The move buttons are real buttons, reachable by keyboard and disabled at the ends of the list. The grip is decorative and carries no name, because it does nothing the buttons do not.
Nothing announces a completed move. If the reordered content is not visible where focus is — a long list, a list in a scroll container — put the result in a live region yourself.
Sortable props
| Prop | Type | Default | Description |
|---|---|---|---|
onReorderrequired | (from: number, to: number) => void | — | Called with the index moved from and the index moved to. |
SortableItem props
| Prop | Type | Default | Description |
|---|---|---|---|
indexrequired | number | — | This item's position in the list. |
labelrequired | string | — | What the move buttons call this item — a screen reader hears it. |
labels | SortableLabels | — | Translations for the two button names. {item} is replaced with label. |