Skip to main content
Version: v1.x

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 press — longPressDelay (default 250ms) activates the drag
  2. Dragging — dragPhaseSV = 'dragging', hover layer follows finger, hit-testing runs every frame
  3. Receiver change — Callbacks fire when the hover enters/exits receivers
  4. Release — dragPhaseSV = 'releasing', snap animation plays
  5. Snap complete — dragPhaseSV = '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​