Drag & drop

Organism
A reusable drag-and-drop context: wrap a surface in a DragDropProvider, mark droppable regions with DropZone, and make items draggable with Draggable plus a DragHandle grip.

Playground

Every control sets a real prop of DragDropProviderProps. The JSX below updates as you go.
Tasks
Design review
Write the tests
Ship the release
Props
JSX · DragDropProviderProps
<DragDropProvider />

Variants

2 examples from src/organisms/drag-drop/drag-drop.md, each rendered live from its own JSX.
Tasks
Design review
Locked task
Locked item
Pass disabled to a Draggable to pin it in place: its grip dims and becomes inert while the rest of the list stays draggable.
<DragDropProvider>
<DropZone bordered id="list" label="Tasks">
<Column snug>
<Draggable id="a" data={{ id: "a", zone: "list" }} label="Design review">
<Card>
<Row between alignCenter>
<Typography>Design review</Typography>
<DragHandle label="Reorder Design review" />
</Row>
</Card>
</Draggable>
<Draggable id="b" data={{ id: "b", zone: "list" }} disabled label="Locked task">
<Card>
<Row between alignCenter>
<Typography>Locked task</Typography>
<DragHandle label="Locked task" />
</Row>
</Card>
</Draggable>
</Column>
</DropZone>
</DragDropProvider>
To do
Rotate secrets
Draft the review
Doing
SSO rollout
Audit log export
Between columns
A DropZone per column; dragging a card moves it into another column at the drop position. The shared list is a flat array with a zone field, and applyDrop re-homes the card.
<Stateful initial={[
{ id: "t1", zone: "todo", title: "Rotate secrets" },
{ id: "t2", zone: "todo", title: "Draft the review" },
{ id: "t3", zone: "doing", title: "SSO rollout" },
{ id: "t4", zone: "doing", title: "Audit log export" },
]}>
{(cards, setCards) => (
<DragDropProvider>
<Grid columns={2} relaxed>
{["todo", "doing"].map((zone) => (
<DropZone bordered key={zone} id={zone} label={zone === "todo" ? "To do" : "Doing"} onDrop={(e) => setCards(applyDrop(cards, e))}>
<Column snug>
{cards.filter((c) => c.zone === zone).map((c) => (
<Draggable key={c.id} id={c.id} data={c} label={c.title}>
<Card>
<Row between alignCenter>
<Typography>{c.title}</Typography>
<DragHandle label={`Reorder ${c.title}`} />
</Row>
</Card>
</Draggable>
))}
</Column>
</DropZone>
))}
</Grid>
</DragDropProvider>
)}
</Stateful>

Anatomy

Tasks
Design review
Write the tests
Ship the release
1
Drop zone
2
Draggable
3
Lifted item
4
Drop indicator
Dimmed parts appear only in some states or variants.

Props

3 props on DragDropProviderProps, grouped as they are declared in source. Required props are marked *.
Prop
Type
Default
Description
Props
3 props
children *
ReactNode
–
style
StyleProp<ViewStyle>
–
Outer layout composition only (width/flex within a parent), never a restyle hook. The provider wraps the whole draggable surface and hosts the floating drag ghost.
testID
string
–
E2E hook forwarded to the root element.

Accessibility and keyboard

Keyboard
Space
Lifts or drops the focused card
Arrow keys
Moves a lifted card
Escape
Cancels the move
Semantics
Roles
button
States
disabled
ARIA on web
aria-disabled
aria-pressed
From the docs
Items lift into a floating ghost and reorder within or across zones, position-aware. It runs on iOS, Android, and the web from one PanResponder + Animated code path, and the grip is keyboard- and screen-reader-operable: press Space to grab, the arrow keys to move, Space to drop, Escape to cancel.

Platform skins

The playground's props on each platform: same tree, native shapes, type and touch sizes.
To do
Rotate secrets
Draft the review
Doing
SSO rollout
Audit log export
Web
control 10 · field 10 · card 16 · Instrument Sans
To do
Rotate secrets
Draft the review
Doing
SSO rollout
Audit log export
iOS
capsule controls · field 8 · card 12 · SF Pro · 44 pt
To do
Rotate secrets
Draft the review
Doing
SSO rollout
Audit log export
Android
Material 3 stadium · field 4 · card 12 · Roboto · 48 dp

Usage

<Stateful initial={[
{ id: "a", zone: "list", title: "Design review" },
{ id: "b", zone: "list", title: "Write the tests" },
{ id: "c", zone: "list", title: "Ship the release" },
]}>
{(cards, setCards) => (
<DragDropProvider>
<DropZone bordered id="list" label="Tasks" onDrop={(e) => setCards(applyDrop(cards, e))}>
<Column snug>
{cards.map((c) => (
<Draggable key={c.id} id={c.id} data={c} label={c.title}>
<Card>
<Row between alignCenter>
<Typography>{c.title}</Typography>
<DragHandle label={`Reorder ${c.title}`} />
</Row>
</Card>
</Draggable>
))}
</Column>
</DropZone>
</DragDropProvider>
)}
</Stateful>
Items lift into a floating ghost and reorder within or across zones, position-aware. It runs on iOS, Android, and the web from one PanResponder + Animated code path, and the grip is keyboard- and screen-reader-operable: press Space to grab, the arrow keys to move, Space to drop, Escape to cancel.
The reading direction is respected throughout. In a right-to-left locale a horizontal zone lays its items out right to left, so the first position is the rightmost one, the drop indicator meets the row's right edge, and the two horizontal arrows swap roles (Left Arrow moves forward, Right Arrow back, per the WAI-ARIA practices). Vertical lists and the up/down arrows read the same way in both directions.

Tokens

Read from the playground render. Values follow the scheme and the ion hue.
--fd
--fm
--ft
--ion
--ion-glow
--ion-ring
--ion-soft
--ln
--ln2
--sf
--sh-lg
--tx3

Related components