Reorder
Primitive

Reorder

Headless primitives for manually arranging one controlled linear collection. Reorder composes DragDrop with ordered-list semantics, identity-array updates, keyboard movement, and direct single-activation movement controls.

Live behavior

Reorder in motion

Interactive
Atom behavior · App-owned appearance
Preparing behavior…

Drag a handle, use the keyboard, or use the visible buttons to change the release order.

waiting for input

When to Use

Use Reorder when a person decides and saves the order of items in one list. Do not use it for automatic sorting by name, date, or status. Tables, trees, Kanban boards, and movement between containers need component-specific APIs that may reuse DragDrop internally.

Features

The behavior Atom owns before your product adds appearance.

  • Controlled stable-identity order.
  • Native ordered-list and list-item defaults.
  • Mouse, pen, delayed-touch, and keyboard reordering.
  • Direct move before, after, to start, and to end controls.
  • Localizable instructions and live announcements.
  • Disabled, read-only, per-item disabled, vertical, horizontal, and RTL modes.
  • Drop-position state for styled layers without Atom-owned visuals.

Import

tsx
import { Reorder } from "@flowstack-ui/atom";

Anatomy

tsx
<Reorder.Root>
  <Reorder.Item>
    <Reorder.Handle />
    <Reorder.MoveBefore />
    <Reorder.MoveAfter />
    <Reorder.MoveToStart />
    <Reorder.MoveToEnd />
    <Reorder.DropIndicator />
  </Reorder.Item>
</Reorder.Root>

API Reference

Root

Owns the controlled identity order and renders an ol by default.

PropTypeDefault
itemsstring[]required
onItemsChange(items, details) => voidrequired
getItemLabel(value) => stringrequired
orientation"vertical" | "horizontal""vertical"
disabledbooleanfalse
readOnlybooleanfalse
instructionsstringEnglish keyboard instructions
messagesDragDropMessagesEnglish defaults
asChildbooleanfalse
renderRenderProp-
Data attributeValues
[data-slot]"reorder"
[data-orientation]"vertical" or "horizontal"
[data-disabled]Present when disabled
[data-readonly]Present when read-only

onItemsChange details include activeValue, overValue, position, previousIndex, nextIndex, and input (keyboard, pointer, or control).

Item

Registers one stable identity and renders an li by default.

PropTypeDefault
valuestringrequired
disabledbooleanfalse
renderRenderProp-
Data attributeValues
[data-slot]"reorder-item"
[data-value]Item identity
[data-dragging]Present while active
[data-drop-target]Present while targeted
[data-drop-position]"before" or "after" while targeted
[data-disabled]Present when disabled

Handle

Native button that starts and controls dragging. It must receive an accessible name such as Move Verify production.

PropTypeDefault
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
aria-describedbyGenerated instruction ID
Data attributeValues
[data-slot]"reorder-handle"
[data-dragging]Present while active

MoveBefore

Native button that immediately moves the Item one logical position earlier. It disables at the first boundary.

MoveAfter

Native button that immediately moves the Item one logical position later. It disables at the last boundary.

MoveToStart

Native button that immediately moves the Item to the first position.

MoveToEnd

Native button that immediately moves the Item to the last position.

All movement buttons accept native button props, asChild, render, and consumer content. They emit [data-slot] and [data-move], and require an authored accessible name.

DropIndicator

Decorative span that exposes the proposed insertion position.

PropTypeDefault
renderRenderProp-
ARIA attributeValues
aria-hiddentrue
Data attributeValues
[data-slot]"reorder-drop-indicator"
[data-state]"active" or "inactive"
[data-position]"before" or "after" while active

Examples

tsx
import { useState } from "react";
import { Reorder } from "@flowstack-ui/atom";

const labels: Record<string, string> = {
  verify: "Verify production",
  approve: "Request approval",
  deploy: "Deploy release",
};

export function ReorderExample() {
  const [items, setItems] = useState(["verify", "approve", "deploy"]);

  return (
    <Reorder.Root
      items={items}
      getItemLabel={(value) => labels[value] ?? value}
      onItemsChange={(nextItems) => setItems(nextItems)}
    >
      {items.map((value) => (
        <Reorder.Item key={value} value={value}>
          <Reorder.Handle aria-label={`Move ${labels[value]}`}>
            Move
          </Reorder.Handle>
          <span>{labels[value]}</span>
          <Reorder.MoveBefore aria-label={`Move ${labels[value]} up`}>Up</Reorder.MoveBefore>
          <Reorder.MoveAfter aria-label={`Move ${labels[value]} down`}>Down</Reorder.MoveAfter>
          <Reorder.DropIndicator />
        </Reorder.Item>
      ))}
    </Reorder.Root>
  );
}

Accessibility

Root and Item use native ordered-list semantics. Handles and movement operations are native buttons. The handle receives generated instructions and Root announces movement with human labels and one-based positions. Keep movement buttons visibly available: they are the simple-pointer alternative required by WCAG 2.2 Dragging Movements.

KeyDescription
Space / EnterPicks up from a Handle; commits while active.
Arrow Up / Arrow DownMoves within a vertical list.
Arrow Left / Arrow RightMoves within a horizontal list and mirrors in RTL.
Home / EndMoves to the first or last position.
EscapeCancels without changing the identity order.

Use stable record IDs as value, render Items in the exact order supplied to Root, and preserve those keys after updates so focus remains on the moved control. Applications own persistence, Undo, failures, and conflicts.

Changelog

Unreleased

  • No unreleased changes.

0.23.0

  • Added the initial controlled linear reorder preset with drag, keyboard, direct movement controls, announcements, direction support, and drop state.