RoadieRoadie
  • Home
  • Brand
  • Foundations
  • Content
  • Tokens
  • Components
  • Charts
  • Widgets
  • Appearance
HomeBrandFoundations
Appearance

Components

  • Navigation

    • Breadcrumb
    • LinkBeta
    • NavigatorAlpha
    • Steps
    • Tabs
  • Layout

    • Accordion
    • CollapsibleBeta
    • PaneAlpha
    • ScrollArea
    • Separator
  • Actions

    • Button
    • IconButton
    • ToggleBeta
    • Toggle groupBeta
    • ToolbarBeta
  • Fields

    • EditorAlpha
    • Field
    • Fieldset
    • Input
    • Label
    • Number fieldBeta
    • OTP fieldBeta
    • Query fieldAlpha
    • Textarea
  • Choices

    • Autocomplete
    • CheckboxBeta
    • Combobox
    • Radio group
    • Select
    • SliderBeta
    • SwitchBeta
  • Date and time

    • CalendarBeta
    • CalendarTile
    • Countdown
    • Dashboard periodBeta
    • Date fieldBeta
    • Date pickerBeta
    • Date range pickerBeta
    • DateTime
    • Duration
    • Time fieldBeta
  • Collections

    • Card
    • Carousel
    • DataTableBeta
    • ListAlpha
    • Marquee
    • RecordGridAlpha
    • RecordTableAlpha
    • SortableAlpha
    • TableBeta
  • Overlays

    • Dialog
    • Drawer
    • MenuBeta
    • Popover
    • ToastBeta
    • Tooltip
  • Status

    • Badge
    • CalloutBeta
    • DeltaBeta
    • Empty state
    • MeterBeta
    • ProgressBeta
    • SkeletonBeta
    • SparklineBeta
    • StatTileBeta
  • Media & brand

    • AvatarBeta
    • Icon Tile
    • Image
    • Logo
    • PatternBeta
    • QR codeBeta
    • SpotIllustration
  • Text

    • Code
    • Highlight
    • KbdBeta
    • Mark
    • Prose

Sortable

Status
Alpha
Added
Components 2.16.020 Oct 2026
Docs updated
9 Oct 2026

A drag-to-reorder wrapper for lists and rows, with a Move menu on each handle for keyboard and screen reader users.

Import

import { Sortable } from '@oztix/roadie-components/sortable'

Examples

Default

Wrap a List in Sortable and give each List.Item a value. The row takes a drag handle in front. Pass the values in order as items, and store the new order in onReorder. Drag a handle to move its row, or click it to open the Move menu. The drag preview is the browser's own snapshot of the row.

function Columns() {
const names = {
date: 'Date',
event: 'Event',
sku: 'SKU',
price: 'Price',

Variants

Set orientation='horizontal' for items in a row. Drops land on the left or right edge, and the menu moves items left and right, or to the start and end.

function Stages() {
const [order, setOrder] = useState(['Doors', 'Support', 'Headliner', 'Encore'])
return (
<Sortable items={order} onReorder={setOrder} orientation='horizontal' label='running order'>
<div className='flex flex-wrap gap-2'>
{order.map((value) => (

States

disabled on Sortable turns off dragging and the Move menu for every item. disabled on one Sortable.Item stops that item being dragged or moved from its own menu; other items can still move past it. A List.Item has no per-row disabled, so build a row you need to hold back with Sortable.Item.

function Columns() {
const [order, setOrder] = useState(['Event', 'Date', 'SKU'])
return (
<div className='grid gap-4'>
<div className='grid gap-2'>
<p className='text-sm text-subtle'>Disabled</p>

Composition

Sortable.Item renders a <div> by default. Pass render to swap the element, such as <li /> inside your own list, and put a Sortable.Handle anywhere inside it. Each item sets data-dragging while it is dragged and data-drop-edge (top, bottom, left or right) while it is the drop target, for your own styling; the dimmed item and the accent drop line are built in. Give each item a label so the handle and menu can name it: "Reorder Sydney", "Move Sydney up".

function Cities() {
const [order, setOrder] = useState(['Brisbane', 'Sydney', 'Melbourne', 'Perth'])
return (
<Sortable items={order} onReorder={setOrder} label='tour stops'>
<ol className='grid gap-2'>
{order.map((value) => (

With a popover

Drag and drop works inside a modal Popover. A long list scrolls by itself while you drag near its top or bottom edge.

function ColumnSettings() {
const [order, setOrder] = useState(
Array.from({ length: 12 }, (_, index) => `Column ${index + 1}`)
)
return (
<Popover modal>

Guidelines

Wrap the list, keyed by value

<Sortable items={order} onReorder={setOrder} label='columns'>
<List>
{order.map((value) => (
<List.Item key={value} value={value} title={names[value]} />
))}
</List>

Do

Put Sortable around the List and key each row by its value, so focus returns to the handle of the row that moved.

<List>
<Sortable items={order} onReorder={setOrder}>
{order.map((value, index) => (
<List.Item key={index} value={value} title={value} />
))}
</Sortable>

Don’t

Don't put it inside the list or key rows by index. Sortable adds a hidden live region after its children, which can't sit inside a list, and index keys send focus to the wrong handle.

Name the items and the collection

<Sortable label='columns' …>
<Sortable.Item value='sku' label='SKU'>…</Sortable.Item>
</Sortable>

Do

Give each item a label (a text List.Item title does this for you) and the root a label. The handle reads "Reorder SKU" and moves are announced as "SKU moved to position 3 of 7 columns".

<Sortable.Item value='sku'>
<SkuIcon />
</Sortable.Item>

Don’t

Don't leave an item unnamed. Every handle then reads "Reorder", and a screen reader user can't tell the rows apart.

Keep rows static and drag from the handle

<List.Item value='sku' title='SKU' trailing={<Toggle … />} />

Do

Put actions in the row's trailing slot as buttons or toggles. A reorderable row is not a link or button, and ignores href, onClick, current and chevron.

<List.Item value='sku' title={<input … />} />

Don’t

Don't put text fields in a sortable item. The whole item is draggable, so its text can't be selected, and Firefox can't place the caret in a field inside it.

Accessibility

  • Keyboard and screen readers: there is no keyboard dragging. The handle is a menu button. Enter or Space opens the Move menu, and moves past the ends are disabled. After a move, focus returns to the moved item's handle.
  • Announcements: every move, by menu or by drag, is announced in a polite live region: "SKU moved to position 3 of 7 columns".
  • Pointer: a press on the handle that turns into a drag never opens the menu. A click without a drag does.
  • Motion: the dragged item fades without a transition when reduced motion is on. In forced colours the drop line uses the system highlight colour.

API reference

Sortable

itemsreadonly string[]
Required

Each item's `value`, in order.

onReorder(next: string[], move: SortableMove) => void
Required

Called with the new order after a drag or a Move menu choice.

orientation"horizontal" | "vertical"

The direction items run in, which sets the drop edges and menu wording.

Defaults to 'vertical'.

disabledboolean

Turns off dragging and the Move menu.

Defaults to false.

labelstring

Names the collection in announcements, as in "SKU moved to position 3 of 7 columns".

Sortable.Handle

Drags its item, and opens a Move menu on click or Enter for anyone not dragging with a pointer.

classNamestring | ((state: ButtonState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

intent"neutral" | "brand" | "brand-secondary" | "brand-blue" | "brand-orange" | "brand-purple" | "accent" | "danger" | "success" | "warning" | "info" | null
emphasis"strong" | "normal" | "subtle" | "subtler" | null

Defaults to subtler.

size"xs" | "sm" | "md" | "lg" | "icon-xs" | "icon-sm" | "icon-md" | "icon-lg"

Icon-button sizing. Use `'xs' | 'sm' | 'md' | 'lg'` (plain) — the `'icon-*'` aliases are accepted for backwards compatibility but discouraged.

Defaults to sm.

aria-labelstring

Defaults to "Reorder {label}" from the item's `label`.

Inherited from ButtonProps

focusableWhenDisabledboolean

Whether the button should be focusable when disabled.

Defaults to false.

Inherited from NativeButtonProps

nativeButtonboolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).

Defaults to true.

Sortable.Item

One reorderable item. Sets `data-dragging` and `data-drop-edge` for styling.

valuestring
Required

Identifies the item; one of the root's `items`.

labelstring

The item's name in the Move menu and announcements, as in "Move SKU down".

disabledboolean

Stops this item being dragged or moved from its own Move menu. Other items can still move past it.

Defaults to false.

renderRoadieRenderProp

Swaps the default `<div>`, e.g. `<li />` inside a list.

Previous page← RecordTableNext pageTable →