orn-ui
Organism

ReorderableList

Requires <UIProvider> — Getting Started →

Installation

Install just this component (copies the source into your project, no npm dependency)

npx orn-ui add reorderable-list

Or install the whole package and import it

pnpm add orn-ui
import { ReorderableList } from 'orn-ui/reorderable-list';
Depends on:core (theme + icons)

Usage

  • when to use — A short, fully-loaded list people rearrange by hand: playlists, priority lists, custom sort order.
  • itemHeight — Required and fixed — the drop index is computed from drag distance, not from measuring layout.
  • onReorder — Fires on release with the array already reordered; `data` itself is never mutated.
  • disabled — Rows still render, dragging just stops responding — no separate read-only variant needed.
Demo clip not recorded yet — see MEDIA.md

Variants

Long-press a row and drag — the rows in between shift out of the way

<ReorderableList
  data={data}
  keyExtractor={(item) => item.id}
  itemHeight={ITEM_HEIGHT}
  onReorder={setData}
  renderItem={(item, _index, dragging) => <Row name={item.name} dragging={dragging} />}
/>

disabled — rows render but can’t be dragged

<ReorderableList
  data={INITIAL}
  keyExtractor={(item) => item.id}
  itemHeight={ITEM_HEIGHT}
  disabled
  onReorder={() => {}}
  renderItem={(item, _index, dragging) => <Row name={item.name} dragging={dragging} />}
/>
Full demo source
const variants: VariantDef[] = [
  {
    label: 'Long-press a row and drag — the rows in between shift out of the way',
    content: (
      <ReorderableList
        data={data}
        keyExtractor={(item) => item.id}
        itemHeight={ITEM_HEIGHT}
        onReorder={setData}
        renderItem={(item, _index, dragging) => <Row name={item.name} dragging={dragging} />}
      />
    ),
  },
  {
    label: 'disabled — rows render but can’t be dragged',
    content: (
      <ReorderableList
        data={INITIAL}
        keyExtractor={(item) => item.id}
        itemHeight={ITEM_HEIGHT}
        disabled
        onReorder={() => {}}
        renderItem={(item, _index, dragging) => <Row name={item.name} dragging={dragging} />}
      />
    ),
  },
];
return (
  <>
    <Caption style={{ marginBottom: 8 }}>Web preview doesn’t simulate touch drag — see the gif on this page.</Caption>
    <VariantList variants={variants} />
  </>
);

Props

NameTypeDefaultDescription
dataT[]
keyExtractor(item: T, index: number) => string
itemHeightnumberFixed height of each row: needed to calculate the target index without measuring layout async.
renderItem(item: T, index: number, dragging: boolean) => ReactElement<unknown, string | JSXElementConstructor<any>>
onReorder(data: T[]) => voidCalled on release, with the array already reordered. Does not mutate `data`.
onDragStart?(index: number) => voidStarts dragging row `index`. Use it to turn off the container's scroll (`scrollEnabled={false}`) while it lasts: on iOS the parent `UIScrollView` cancels touches on the content as soon as its vertical gesture starts, and without this the drag dies within a few pixels.
onDragEnd?() => voidEnds the drag — on release or cancellation. Always runs if `onDragStart` ran.
disabled?booleanfalse
style?StyleProp<ViewStyle>