Skip to content

Components · Advanced interaction

ReorderableList

A list reordered by dragging a handle (mouse, pen, touch) or fully from the keyboard, with every step announced.

@rookcom/monarch-react/dataComponent

Drag or keyboard

  • New12
  • Contacted8
  • Proposal sent5
  • Negotiation3
Press Space or Enter to pick up. Use the arrow keys to move, Space or Enter to drop, Escape to cancel.

Usage examples

Move buttons and a locked item

Purpose

Lets people put a short list in their own order, such as pipeline stages, form fields, banners or approval steps, by drag or keyboard, with the same result. Import from @rookcom/monarch-react/data.

Use when

  • The order of a list is user data the app saves: stages, sections, fields, priorities.
  • A settings list where “Move up / Move down” buttons are the accessible fallback today.

Avoid when

  • Moving cards between columns (a board): this is a single vertical list.
  • Sorting by a value: offer a sort control instead of manual order.
  • Very long or virtualised lists: auto-scrolling while dragging is not provided.

Design principles

  • Drag only from the grip handle, so text in the row stays selectable and buttons in it stay clickable.
  • Keyboard is first-class, not a fallback: the same move happens with Space, arrows and Space, and Escape always undoes it.
  • Say what happened: “Picked up”, “moved to position 3 of 5”, “dropped”, “cancelled” go to a live region.
  • Nothing is saved until the drop: onReorder fires once per finished move with the new order, ready for one API call.
  • Rows glide to their new place (a FLIP transition) unless the user prefers reduced motion.
  • No drag-and-drop library: pointer events with capture cover mouse, pen and touch in a few hundred lines.

API reference

import { ReorderableList, moveItem } from '@rookcom/monarch-react/data';
ExportKindRenderingRoot class
ReorderableListcomponentclient.mn-reorderable-
moveItemutilityclient–

Generic over the item type: ReorderableList<T> with getKey, getLabel and renderItem(item, { index, active, disabled }).

Renders <div class="mn-reorderable-list"> with a <ul> (the ref and the rest of the props go to the list) and two visually hidden helpers: the instructions and the live region.

onReorder(items, { item, from, to }) gets a new array; moveItem(list, from, to) is exported for apps that move items elsewhere (for example after an undo).

Generated from the TypeScript declarations of packages/react/src/components/interaction/reorderable-list.

Properties

ReorderableList

A list the user can reorder by dragging a handle (mouse, pen or touch) or from the keyboard: Space picks an item up, the arrow keys move it, Space drops it and Escape cancels. Every step is announced. Optional move buttons give a one-click alternative.

PropTypeDescription
getKey required(item: T) => stringA stable, unique key for each item.
getLabel required(item: T) => stringPlain-text name of each item, used in labels and announcements.
renderItem required(item: T, state: ReorderableListItemState) => ReactNodeDraws the row's content; the handle and move buttons are added around it.
classNamestring
defaultItemsreadonly T[]The starting order when uncontrolled.
disabledbooleanNo item can be moved: handles and buttons are disabled.
idstring
isItemDisabled((item: T) => boolean)An item that cannot be moved. Other items can still move past it.
itemsreadonly T[]The items in order (controlled). Update it from `onReorder`.
messagesPartial<ReorderableListMessages>Override labels and announcements.
moveButtonsbooleanAlso show Move up / Move down buttons on each row: a single-click alternative for people who cannot drag or hold keys.
onReorder((items: T[], move: ReorderableListMove<T>) => void)Called once per finished move (drop, Space, or a move button) with the new order.
refForwardedRef<HTMLUListElement>

Variants

PropValuesDescription
moveButtons'false''true'Adds Move up / Move down buttons to each row (disabled at the ends): a single-click alternative for switch and voice users.
isItemDisabled / disabled'(item) => boolean''boolean'Locks single items (others can still move past them) or the whole list.

States

StateExposed asBehaviour
Picked up (keyboard)data-active="keyboard" on the row, aria-pressed="true" on the handleRaised shadow and accent border; the handle is tinted.
Dragging (pointer)data-active="pointer" on the row, data-dragging="pointer" on the rootThe row follows the pointer with a raised shadow; others make room.
Lockeddata-disabled on the row, disabled handle and buttonsThe handle shows the disabled colour and a not-allowed cursor.
Focus–Handles and buttons show the Monarch focus ring on :focus-visible.

Interaction behaviour

  • Pointer: press the handle and move more than 4px to start; the row follows the pointer and the list reorders as its centre passes others; release to drop. pointercancel (for example a system gesture) cancels.
  • The handle sets touch-action: none, so on touch screens dragging the handle never scrolls the page; the rest of the row scrolls normally.
  • Keyboard moves are previewed in place and committed on drop; Escape, Tab away, or focus leaving the list cancels and restores the order.
  • Controlled with items + onReorder, or uncontrolled with defaultItems. A controlled list that does not update items snaps back.
  • If the moving item disappears from items mid-move, the move ends quietly.

Keyboard support

KeysAction
TabMoves between handles (and move buttons).
Space / EnterPicks up the focused item; pressed again, drops it.
ArrowUp / ArrowDown (or Left / Right)Moves the picked-up item one place.
Home / EndMoves the picked-up item to the start or end.
EscapeCancels the move (keyboard or pointer) and restores the order.

Accessibility

Role
list with button handles
Accessible name
Give the list an aria-label (“Pipeline stages”). Each handle is “Reorder <label>” (messages.handleLabel).
  • Every handle is described by one visually hidden instruction (aria-describedby).
  • Pick-up, each move, drop and cancel are announced in an assertive live region with the position (“position 3 of 5”).
  • Focus stays on the moved item’s handle after every keyboard move and button press (or its handle when the button became disabled).
  • Move buttons are named per item (“Move Email address up”).
  • Verified with axe in jsdom and in Chromium, Firefox and WebKit on /components/reorderable-list.

Responsive behaviour

  • Rows fill their container and wrap long content; nothing has a fixed width, so it works at 320px.
  • On coarse pointers handles and move buttons grow to --mn-control-md (40px) touch targets.

Theme support

  • Monarch tokens only: surface rows with a subtle border; the active row uses --mn-color-accent-border and --mn-shadow-md.
  • Row transitions are removed under prefers-reduced-motion: reduce and by the FLIP code itself.
  • Forced-colours mode outlines the active row in Highlight.

Do and don't

Do

  • Give every item a short, unique getLabel: it is what screen-reader users hear.
  • Save once in onReorder, and roll back (pass the old items) if the save fails.
  • Turn on moveButtons in settings screens where reordering is the main task.

Don't

  • Do not put drag handlers on the whole row; keep the handle.
  • Do not reorder on every intermediate move: wait for onReorder.
  • Do not use it for boards with columns or for lists of hundreds of rows.
  • DataTable: Tabular data the user sorts by a column rather than by hand.
  • Stepper: Shows progress through an order that is fixed, not edited.

Dependencies

KindDepends on
npm packagesreact
Monarch componentsNone

Testing coverage

  • Named list, labelled and described handles, axe.
  • Keyboard: Space picks up, arrows move with focus kept, Space drops and calls onReorder once with { item, from, to }; each step announced.
  • Escape cancels and restores; Home/End; moves stop at the edges; arrows do nothing before pick-up; Tab away cancels.
  • Controlled: follows items, and snaps back when the parent ignores onReorder.
  • Move buttons: disabled at the ends, announce, keep focus on the moved item; axe.
  • Locked items and a disabled list; other items move past a locked one.
  • Pointer: threshold before a drag, reorder by row centres, data-active, drop commits; pointercancel and Escape cancel.
  • Translated messages; moveItem clamps and copies; renders on the server.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery/reorderable-list; Cross-browser axe: /components/reorderable-list

Tests: packages/react/src/components/interaction/reorderable-list