docs/enterprise/drag-and-drop.md
import reflex as rx
import reflex_enterprise as rxe
Reflex Enterprise provides comprehensive drag and drop functionality for creating interactive UI elements using the rxe.dnd module. Built on top of react-dnd, it offers both high-level components for common use cases and low-level hooks for advanced scenarios.
# Important: Always decorate functions defining `rxe.dnd.draggable` components with `@rx.memo` to avoid compilation errors.
Here's a basic example showing how to create a draggable item and drop target:
import reflex as rx
import reflex_enterprise as rxe
class BasicDndState(rx.State):
drop_count: int = 0
def increment_drop_count(self):
self.drop_count += 1
@rx.memo
def draggable_card() -> rxe.dnd.Draggable:
return rxe.dnd.draggable(
rx.card(
rx.text("Drag me!", weight="bold"),
rx.text("I can be moved around"),
bg="blue.500",
color="white",
p=4,
cursor="grab",
width="200px",
height="100px",
),
type="BasicCard",
item={"message": "Hello from draggable!"},
)
def basic_drag_drop():
return rx.vstack(
rx.text(f"Items dropped: {BasicDndState.drop_count}"),
rx.hstack(
draggable_card(),
rxe.dnd.drop_target(
rx.box(
"Drop Zone",
bg="gray.100",
border="2px dashed gray",
min_height="150px",
min_width="200px",
display="flex",
align_items="center",
justify_content="center",
font_weight="bold",
),
accept=["BasicCard"],
on_drop=BasicDndState.increment_drop_count,
),
spacing="4",
align="start",
),
spacing="4",
)
Create a draggable item that can be moved between multiple drop targets:
import reflex as rx
import reflex_enterprise as rxe
class MultiPositionState(rx.State):
card_position: int = 0
def set_card_position(self, position: int):
self.card_position = position
@rx.memo
def movable_card() -> rxe.dnd.Draggable:
return rxe.dnd.draggable(
rx.card(
rx.text("Movable Card", weight="bold"),
rx.text("Position: " + MultiPositionState.card_position.to_string()),
bg="purple.500",
color="white",
p=4,
width="180px",
height="120px",
),
type="MovableCard",
border="2px solid purple",
)
def drop_zone(position: int):
params = rxe.dnd.DropTarget.collected_params
return rxe.dnd.drop_target(
rx.cond(
MultiPositionState.card_position == position,
movable_card(),
rx.box(f"Drop Zone {position}", color="gray.600", font_weight="bold"),
),
width="200px",
height="200px",
border="2px solid red",
border_color=rx.cond(params.is_over, "green.500", "red.500"),
bg=rx.cond(params.is_over, "green.100", "blue.100"),
accept=["MovableCard"],
on_drop=lambda _: MultiPositionState.set_card_position(position),
display="flex",
align_items="center",
justify_content="center",
)
def multi_position_example():
return rx.vstack(
rx.text("Drag the card between positions", weight="bold"),
rx.grid(
drop_zone(0),
drop_zone(1),
drop_zone(2),
drop_zone(3),
columns="2",
spacing="4",
),
spacing="4",
)
Access drag and drop state information using collected parameters:
import reflex as rx
import reflex_enterprise as rxe
class StateTrackingState(rx.State):
drag_info: str = "No drag activity"
def set_drag_info(self, value: str):
self.drag_info = value
@rx.memo
def tracked_draggable() -> rxe.dnd.Draggable:
drag_params = rxe.dnd.Draggable.collected_params
return rxe.dnd.draggable(
rx.card(
rx.text("Tracked Draggable"),
rx.text(rx.cond(drag_params.is_dragging, "Dragging...", "Ready to drag")),
bg=rx.cond(drag_params.is_dragging, "orange.500", "blue.500"),
color="white",
p=4,
opacity=rx.cond(drag_params.is_dragging, 0.5, 1.0),
),
type="TrackedItem",
on_end=StateTrackingState.set_drag_info("Drag ended"),
)
def tracked_drop_target():
drop_params = rxe.dnd.DropTarget.collected_params
return rxe.dnd.drop_target(
rx.box(
rx.text("Smart Drop Zone"),
rx.text(rx.cond(drop_params.is_over, "Ready to receive!", "Waiting...")),
bg=rx.cond(drop_params.is_over, "green.200", "gray.100"),
border=rx.cond(drop_params.is_over, "2px solid green", "2px dashed gray"),
p=4,
min_height="150px",
display="flex",
flex_direction="column",
align_items="center",
justify_content="center",
),
accept=["TrackedItem"],
on_drop=StateTrackingState.set_drag_info("Item successfully dropped!"),
on_hover=StateTrackingState.set_drag_info("Item hovering over drop zone"),
)
def state_tracking_example():
return rx.vstack(
rx.text(f"Status: {StateTrackingState.drag_info}"),
rx.hstack(tracked_draggable(), tracked_drop_target(), spacing="4"),
spacing="4",
)
Create dynamic draggable lists using rx.foreach. Always pass a stable item
identifier as the key prop to the outermost component rendered by the foreach
to ensure item identity is trackable as the item in the underlying list is
rearranged.
import dataclasses
import reflex as rx
import reflex_enterprise as rxe
@dataclasses.dataclass
class ListItem:
id: str
text: str
list_id: str
class DynamicListState(rx.State):
list_a: list[ListItem] = [
ListItem(id="1", text="Item 1", list_id="A"),
ListItem(id="2", text="Item 2", list_id="A"),
ListItem(id="3", text="Item 3", list_id="A"),
]
list_b: list[ListItem] = [
ListItem(id="4", text="Item 4", list_id="B"),
ListItem(id="5", text="Item 5", list_id="B"),
]
def move_item(self, item_data: dict, target_list: str):
item_id = item_data.get("id")
source_list = item_data.get("list_id")
if not item_id or not source_list:
return
# Find the item in the source list
source_items = getattr(self, f"list_{source_list.lower()}")
item_to_move = None
for item in source_items:
if item.id == item_id:
item_to_move = item
break
if not item_to_move:
return
# Remove from source list only
if source_list == "A":
self.list_a = [item for item in self.list_a if item.id != item_id]
else:
self.list_b = [item for item in self.list_b if item.id != item_id]
# Create new item for target list
new_item = ListItem(id=item_id, text=item_to_move.text, list_id=target_list)
# Add to target list
if target_list == "A":
self.list_a.append(new_item)
else:
self.list_b.append(new_item)
@rx.memo
def draggable_list_item(item: rx.Var[ListItem]) -> rx.Component:
return rxe.dnd.draggable(
rx.card(
rx.text(item.text, weight="bold"),
rx.text(f"From List {item.list_id}", size="2", color="gray.600"),
p=3,
cursor="grab",
_hover={"bg": "gray.50"},
),
type="ListItem",
item={"id": item.id, "text": item.text, "list_id": item.list_id},
)
def droppable_list(title: str, items: list[ListItem], list_id: str):
return rxe.dnd.drop_target(
rx.vstack(
rx.text(title, weight="bold", size="5"),
rx.vstack(
rx.foreach(
items,
lambda item, index: draggable_list_item(item=item, key=item.id),
),
spacing="2",
min_height="200px",
width="100%",
),
bg="gray.50",
p=4,
border_radius="md",
border="2px dashed gray",
width="250px",
),
accept=["ListItem"],
on_drop=lambda item: DynamicListState.move_item(item, list_id),
)
def dynamic_list_example():
return rx.hstack(
droppable_list("List A", DynamicListState.list_a, "A"),
droppable_list("List B", DynamicListState.list_b, "B"),
spacing="6",
align="start",
)
The rxe.dnd.draggable component makes any element draggable:
Key Properties:
type: String identifier for drag type matchingitem: Data object passed to drop handlerson_end: Called when drag operation endsThe rxe.dnd.drop_target component creates areas that accept draggable items:
Key Properties:
accept: List of drag types this target acceptson_drop: Called when item is droppedon_hover: Called when item hovers over targetAccess real-time drag/drop state:
Draggable Parameters (rxe.dnd.Draggable.collected_params):
is_dragging: Boolean indicating if item is being draggedDrop Target Parameters (rxe.dnd.DropTarget.collected_params):
is_over: Boolean indicating if draggable is hoveringcan_drop: Boolean indicating if drop is allowedCreates a draggable component that can be moved around the interface.
Parameters:
type (str, required): String identifier that must match the accept list of drop targetsitem (dict | Callable): Data object passed to drop handlers. Can be a static dictionary or a function that receives a DragSourceMonitor and returns datapreview_options (dict): Configuration for the drag preview appearanceoptions (dict): Additional drag source options like dropEffecton_end (EventHandler): Event handler called when drag operation completescan_drag (Callable): Function that determines if the item can be draggedis_dragging (Callable): Function to override the default dragging state detectioncollect (Callable): Function to collect custom properties from the drag monitorCreates a drop target that can receive draggable items.
Parameters:
accept (str | list[str], required): Drag type(s) this target acceptsoptions (dict): Additional drop target configuration optionson_drop (EventHandler): Event handler called when an item is dropped, receives the item dataon_hover (EventHandler): Event handler called when an item hovers over the targetcan_drop (Callable): Function that determines if a specific item can be droppedcollect (Callable): Function to collect custom properties from the drop monitorProvides information about the drag operation state:
is_dragging(): Returns True if this item is currently being draggedcan_drag(): Returns True if the item can be draggedget_item(): Returns the item data being draggedget_item_type(): Returns the drag type stringget_drop_result(): Returns the drop result (available in on_end)did_drop(): Returns True if the item was successfully droppedProvides information about the drop target state:
is_over(): Returns True if a draggable item is hovering over this targetcan_drop(): Returns True if the hovering item can be droppedget_item(): Returns the item data of the hovering draggableget_item_type(): Returns the drag type of the hovering item{
"is_dragging": bool, # True when this item is being dragged
"can_drag": bool, # True when this item can be dragged
}
{
"is_over": bool, # True when a draggable is hovering
"can_drop": bool, # True when the hovering item can be dropped
"item": dict | None, # Data from the hovering draggable item
}
The item parameter allows you to pass data from draggable components to drop handlers:
import reflex as rx
import reflex_enterprise as rxe
class SimpleState(rx.State):
message: str = "No items dropped yet"
def set_message_from_item(self, item: dict):
self.message = f"Dropped: {item['name']}"
def simple_draggable():
return rxe.dnd.draggable(
rx.box("Drag me!", p=4, bg="blue.100", border="1px solid blue", cursor="grab"),
type="simple",
item={"name": "test_item", "value": 42},
)
def simple_drop_target():
return rxe.dnd.drop_target(
rx.box(
rx.text(SimpleState.message),
p=4,
bg="gray.100",
border="2px dashed gray",
min_height="100px",
),
accept=["simple"],
on_drop=SimpleState.set_message_from_item,
)
def item_data_example():
return rx.vstack(simple_draggable(), simple_drop_target(), spacing="4")
The collect parameter allows you to access drag and drop state information in real-time:
import reflex as rx
import reflex_enterprise as rxe
class CollectState(rx.State):
drag_info: str = "No drag activity"
drop_info: str = "No drop activity"
def handle_drop(self, item: dict):
self.drop_info = f"Dropped: {item.get('name', 'Unknown')}"
return rx.toast(f"Successfully dropped {item.get('name', 'item')}")
def collect_draggable():
params = rxe.dnd.Draggable.collected_params
return rxe.dnd.draggable(
rx.box(
rx.vstack(
rx.text("Drag me!", weight="bold"),
rx.text(f"Dragging: {params.is_dragging}", size="2"),
rx.text(f"Can drag: {params.can_drag}", size="2"),
spacing="1",
),
p=4,
bg=rx.cond(params.is_dragging, "blue.200", "blue.100"),
border="1px solid blue",
cursor=rx.cond(params.is_dragging, "grabbing", "grab"),
opacity=rx.cond(params.is_dragging, 0.7, 1.0),
),
type="collect_item",
item={"id": "collect_test", "name": "Test Item"},
)
def collect_drop_target():
params = rxe.dnd.DropTarget.collected_params
return rxe.dnd.drop_target(
rx.box(
rx.vstack(
rx.text("Drop Zone", weight="bold"),
rx.text(f"Is over: {params.is_over}", size="2"),
rx.text(f"Can drop: {params.can_drop}", size="2"),
rx.cond(
params.item,
rx.text(
f"Hovering item: {params.item.get('name', 'Unknown')}", size="2"
),
rx.text("No item hovering", size="2"),
),
spacing="1",
),
p=4,
bg=rx.cond(
params.is_over & params.can_drop,
"green.200",
rx.cond(params.is_over, "yellow.200", "gray.100"),
),
border=rx.cond(
params.is_over & params.can_drop,
"2px solid green",
rx.cond(params.is_over, "2px solid yellow", "2px dashed gray"),
),
min_height="120px",
),
accept=["collect_item"],
on_drop=CollectState.handle_drop,
)
def custom_collect_example():
return rx.vstack(
rx.text("Real-time Monitor State", weight="bold", size="4"),
rx.hstack(
collect_draggable(), collect_drop_target(), spacing="6", align="start"
),
rx.text(CollectState.drop_info, size="2", color="gray.600"),
spacing="4",
)
Drag and drop functionality requires the rxe.dnd.provider component to wrap your app. The provider is automatically added when using draggable or drop_target components.
For manual control:
def app():
return rxe.dnd.provider(
# Your app content
your_app_content(),
backend="HTML5", # or "Touch" for mobile
)
@rx.memo on functions containing draggable components