Skip to content

Axis Lock

By default, items can be dragged freely in any direction. Axis lock constrains the drag movement to a single axis -- either horizontal or vertical. This is useful for lists, sliders, or any UI where dragging should only occur along one dimension.

Experimental API

The axis lock API is annotated with @ExperimentalDndApi. You must opt in with @OptIn(ExperimentalDndApi::class) to use it.

DragAxis

The DragAxis enum defines the available axis constraints:

Value Description
Free Item can be dragged in any direction (default).
Horizontal Item can only be dragged along the horizontal axis.
Vertical Item can only be dragged along the vertical axis.

Usage with DraggableItem

@OptIn(ExperimentalDndApi::class)
DraggableItem(
    state = dragAndDropState,
    key = "item-1",
    data = "Hello",
    dragAxis = DragAxis.Vertical,
) {
    Text(
        text = "I can only move up and down",
        modifier = Modifier.graphicsLayer {
            alpha = if (isDragging) 0f else 1f
        },
    )
}

Usage with ReorderableItem

Axis lock works naturally with reorderable lists. For a vertical list, lock to the vertical axis so items cannot be dragged sideways:

@OptIn(ExperimentalDndApi::class)
@Composable
fun VerticalLockedReorderExample() {
    val reorderState = rememberReorderState<String>()
    var items by remember {
        mutableStateOf(listOf("Item 1", "Item 2", "Item 3", "Item 4"))
    }

    ReorderContainer(
        state = reorderState,
    ) {
        LazyColumn(
            verticalArrangement = Arrangement.spacedBy(8.dp),
            modifier = Modifier.fillMaxSize().padding(16.dp),
        ) {
            items(items, key = { it }) { item ->
                ReorderableItem(
                    state = reorderState,
                    key = item,
                    data = item,
                    dragAxis = DragAxis.Vertical,
                    onDrop = {},
                    onDragEnter = { state ->
                        items = items.toMutableList().apply {
                            val index = indexOf(item)
                            if (index == -1) return@ReorderableItem
                            remove(state.data)
                            add(index, state.data)
                        }
                    },
                    modifier = Modifier.fillMaxWidth(),
                ) {
                    Card(
                        modifier = Modifier
                            .graphicsLayer {
                                alpha = if (isDragging) 0f else 1f
                            }
                            .fillMaxWidth()
                            .height(56.dp),
                    ) {
                        Box(
                            contentAlignment = Alignment.CenterStart,
                            modifier = Modifier
                                .fillMaxSize()
                                .padding(horizontal = 16.dp),
                        ) {
                            Text(text = item)
                        }
                    }
                }
            }
        }
    }
}

Horizontal Axis Lock

For a horizontal list or row:

@OptIn(ExperimentalDndApi::class)
ReorderableItem(
    state = reorderState,
    key = item,
    data = item,
    dragAxis = DragAxis.Horizontal,
    onDragEnter = { /* reorder logic */ },
) {
    // ...
}

How It Works

When axis lock is active:

  • Vertical -- The drag shadow moves only along the Y axis. Any horizontal movement from the pointer is ignored, keeping the item aligned in its column.
  • Horizontal -- The drag shadow moves only along the X axis. Any vertical movement is ignored.
  • Free -- Both axes are tracked, allowing unconstrained dragging.

Tip

Axis lock is particularly useful combined with auto scroll. For vertical lists, locking to DragAxis.Vertical ensures the drag shadow stays aligned with the list while auto scroll handles navigation.