Skip to main content

Core Concepts

Understanding Drax's architecture helps you build better drag-and-drop interactions. This page covers the foundational concepts.

DraxProvider

Every drag-and-drop area needs a DraxProvider. It:

  • Manages the view registry — tracks all registered DraxView instances
  • Maintains SharedValues for drag state (split by update frequency for performance)
  • Provides the spatial index for UI-thread hit-testing
  • Renders the hover layer (the floating copy of the dragged view)
<DraxProvider>
{/* All DraxViews must be inside a provider */}
</DraxProvider>

You can have multiple providers if you need separate drag contexts.

DraxView

DraxView is the core building block. Any DraxView can play one or more roles:

RoleDescriptionKey Props
DraggableCan be picked up and moveddraggable, payload, onDragStart, onDragEnd
ReceptiveCan receive dropsreceptive, onReceiveDragDrop, acceptsDrag
MonitoringObserves drags happening inside itmonitoring, onMonitorDragStart, onMonitorDragOver

By default, a DraxView is both draggable and receptive. Set draggable={false} or receptive={false} to restrict behavior.

Nesting with isParent

When you nest DraxView components (e.g., a list container with draggable items), the parent needs isParent:

<DraxView isParent monitoring>
<DraxView payload="item-1">...</DraxView>
<DraxView payload="item-2">...</DraxView>
</DraxView>

isParent automatically wraps children in a DraxSubprovider with the correct view reference. This enables proper coordinate transformation between nested views.

Event System

Drax has 19 callback events organized by the view's role:

Dragged View Events

CallbackWhen
onDragStartDrag begins
onDragEvery frame while dragging (no receiver)
onDragEnterDragged into a new receiver
onDragOverEvery frame while over same receiver
onDragExitDragged out of a receiver
onDragEndDrag ends (no receiver / cancelled)
onDragDropDrag ends over a receiver
onSnapEndSnap-back animation completes

Receiver View Events

CallbackWhen
onReceiveDragEnterSomething dragged over this view
onReceiveDragOverEvery frame while something hovers
onReceiveDragExitDragged item leaves / drag cancelled
onReceiveDragDropItem dropped on this view
onReceiveSnapEndSnap animation to this receiver completes

Monitor View Events

CallbackWhen
onMonitorDragStartDrag starts inside monitored area
onMonitorDragEnterDragged into monitored area
onMonitorDragOverEvery frame inside monitored area
onMonitorDragExitLeaves monitored area
onMonitorDragEndDrag ends inside monitored area
onMonitorDragDropDrop occurs inside monitored area

UI-Thread Architecture

Drax uses a UI-thread-first design for maximum performance:

Spatial Index Worklet

All view positions are maintained in a SharedValue<SpatialEntry[]>. On every gesture frame, a worklet runs hit-testing on the UI thread to determine which views are under the finger:

Gesture Update (UI thread)
→ hitTestWorklet(position, spatialIndex, scrollOffsets)
→ Returns: { receiverId, monitorIds }
→ runOnJS(handleReceiverChange) if receiver changed

This means receiver detection happens at 60fps without any JS-thread round-trips.

SharedValue Split

SharedValues are split by update frequency to minimize unnecessary re-renders:

SharedValueUpdate FrequencyRead By
hoverPositionSVEvery frameHoverLayer only
dragAbsolutePositionSVEvery frameGesture worklet
draggedIdSV~2x per dragAll DraxView animated styles
receiverIdSV~3-5x per dragAll DraxView animated styles
dragPhaseSV~3x per dragAll DraxView animated styles
spatialIndexSVOn mount/layoutGesture worklet

Hover Layer

When a drag starts, Drax renders a floating copy of the dragged view in the HoverLayer. This copy:

  • Follows the finger position using translateX/translateY
  • Supports 5 conditional style states (see Hover Styles)
  • Is rendered above all other content (last child of the provider)

The original view can be styled differently while being dragged using draggingStyle, dragReleasedStyle, etc.

Drag Lifecycle

A typical drag goes through these phases:

  1. Long presslongPressDelay (default 250ms) activates the drag
  2. DraggingdragPhaseSV = 'dragging', hover layer follows finger, hit-testing runs every frame
  3. Receiver change — Callbacks fire when the hover enters/exits receivers
  4. ReleasedragPhaseSV = 'releasing', snap animation plays
  5. Snap completedragPhaseSV = 'idle', hover layer clears, onSnapEnd fires

Payload

Payloads are the data transport mechanism between dragged views and receivers:

// Sender
<DraxView payload={{ id: 'task-1', color: 'blue' }}>
<Text>Task 1</Text>
</DraxView>

// Receiver
<DraxView
onReceiveDragDrop={({ dragged }) => {
const { id, color } = dragged.payload;
// Use the payload data
}}
/>

You can also set separate dragPayload and receiverPayload if the view is both draggable and receptive with different data.

Next Steps