An open API service indexing awesome lists of open source software.

https://github.com/calvin-ll/reorderable

Reorder items in Lists and Grids in Jetpack Compose and Compose Multiplatform with drag and drop.
https://github.com/calvin-ll/reorderable

android compose-multiplatform drag-and-drop draggable grid jetpack-compose kotlin list reorderable reorderable-list reordering

Last synced: about 1 year ago
JSON representation

Reorder items in Lists and Grids in Jetpack Compose and Compose Multiplatform with drag and drop.

Awesome Lists containing this project

README

          

# Reorderable

Reorderable is a simple library that allows you to reorder items in [`LazyColumn`](), [`LazyRow`](), [`LazyVerticalGrid`](), [`LazyHorizontalGrid`](), [`LazyVerticalStaggeredGrid`](), and [`LazyHorizontalStaggeredGrid`]() as well as [`Column`]() and [`Row`]() in Jetpack Compose and Compose Multiplatform with drag and drop.

The latest demo app APK can be found in the [releases](https://github.com/Calvin-LL/Reorderable/releases) section under the "Assets" section of the latest release.



LazyColumn
LazyGrid





A video showing an item being reordered in a LazyColumn


A video showing an item being reordered in a LazyGrid


## Used By

- [Lawnchair](https://github.com/LawnchairLauncher/lawnchair/blob/10889eb9772d5ec05f8ec536db3986ad5d9f4f33/build.gradle#L420) ![GitHub Repo stars](https://img.shields.io/github/stars/LawnchairLauncher/lawnchair?style=flat)
- [Pocket Casts](https://github.com/Automattic/pocket-casts-android/blob/96cb6c6c3e210800a5fd2365776eee896079e255/gradle/libs.versions.toml#L281) ![GitHub Repo stars](https://img.shields.io/github/stars/Automattic/pocket-casts-android?style=flat)
- [gkd](https://github.com/gkd-kit/gkd/blob/de140488aa42330d282cc41b92127237308e25f2/gradle/libs.versions.toml#L63) ![GitHub Repo stars](https://img.shields.io/github/stars/gkd-kit/gkd?style=flat)
- [Mihon](https://github.com/mihonapp/mihon/blob/919607cd06ee45ac667a2fd650d85aaf6ebb9762/gradle/libs.versions.toml#L68) ![GitHub Repo stars](https://img.shields.io/github/stars/mihonapp/mihon?style=flat)
- [InnerTune](https://github.com/z-huang/InnerTune/blob/ba3a3a0fe9d3499205a7fc91649938091cad75b8/gradle/libs.versions.toml#L34) ![GitHub Repo stars](https://img.shields.io/github/stars/z-huang/InnerTune?style=flat)
- [ImageToolbox](https://github.com/T8RIN/ImageToolbox/blob/f03ba7e7dd497b215cc14cf80ee4991d42d101a4/gradle/libs.versions.toml#L160) ![GitHub Repo stars](https://img.shields.io/github/stars/T8RIN/ImageToolbox?style=flat)
- [StreetComplete](https://github.com/streetcomplete/StreetComplete/blob/bcb8b58597c5e55b59b71be3568eed5e6a025e9b/app/build.gradle.kts#L154) ![GitHub Repo stars](https://img.shields.io/github/stars/streetcomplete/StreetComplete?style=flat)
- [EhViewer](https://github.com/FooIbar/EhViewer/blob/4bb6b0baf69f4e996e1dd0bdb89a7f112819bf02/gradle/libs.versions.toml#L102) ![GitHub Repo stars](https://img.shields.io/github/stars/FooIbar/EhViewer?style=flat)
- [Twine](https://github.com/msasikanth/twine/blob/841defa05f03c13e56fd331e288f9a5e676862ca/gradle/libs.versions.toml#L124) ![GitHub Repo stars](https://img.shields.io/github/stars/msasikanth/twine?style=flat)
- [bilimiao](https://github.com/10miaomiao/bilimiao2/blob/b806379206283309defd6d0ef9ad3b575dd46642/bilimiao-compose/build.gradle.kts#L83) ![GitHub Repo stars](https://img.shields.io/github/stars/10miaomiao/bilimiao2?style=flat)
- [Neo Launcher](https://github.com/NeoApplications/Neo-Launcher/blob/c3788690e31d13249ae70e9db628ed7e9baa86d4/gradle/libs.versions.toml#L112) ![GitHub Repo stars](https://img.shields.io/github/stars/NeoApplications/Neo-Launcher?style=flat)
- [Stream Chat](https://github.com/GetStream/stream-chat-android/blob/95a3f812991d7fe1e91d7457d125a039aeff704c/buildSrc/src/main/kotlin/io/getstream/chat/android/Dependencies.kt#L217) ![GitHub Repo stars](https://img.shields.io/github/stars/GetStream/stream-chat-android?style=flat)
- [EinkBro](https://github.com/plateaukao/einkbro/blob/4dfa50fff1ced5035d1be77ef5af55ac165375e6/app/build.gradle.kts#L148) ![GitHub Repo stars](https://img.shields.io/github/stars/plateaukao/einkbro?style=flat)

## Features

- Supports Compose Multiplatform (Android, iOS, Desktop/JVM, Wasm, JS)
- Supports items of different sizes
- Some items can be made non-reorderable
- Supports dragging and animating the first visible item
- Supports dragging immediately or long press to start dragging
- Supports section headers and footers
- Scrolls when dragging to the edge of the screen. (unavailable for [`Column`]() and [`Row`]()) The scroll speed is based on the distance from the edge of the screen.
- Uses the new [`Modifier.animateItem`]() API to animate item movement in [`LazyColumn`](), [`LazyRow`](), [`LazyVerticalGrid`](), [`LazyHorizontalGrid`](), [`LazyVerticalStaggeredGrid`](), and [`LazyHorizontalStaggeredGrid`]()
- Supports using a child of an item as the drag handle

## Usage

### Version Catalog

If you're using Version Catalog, add the following to your `libs.versions.toml` file:

```toml
[versions]
#...
reorderable = "2.4.3"

[libraries]
#...
reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" }
```

or

```toml
[libraries]
#...
reorderable = { module = "sh.calvin.reorderable:reorderable", version = "2.4.3" }
```

then

```kotlin
dependencies {
// ...
implementation(libs.reorderable)
}
```

### Gradle

If you're using Gradle instead, add the following to your `build.gradle` file:

#### Kotlin DSL

```kotlin
dependencies {
implementation("sh.calvin.reorderable:reorderable:2.4.3")
}
```

#### Groovy DSL

```groovy
dependencies {
implementation 'sh.calvin.reorderable:reorderable:2.4.3'
}
```

### Examples

See [demo app code](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo) for more examples.

#### Table of Contents

- [`LazyColumn`](#lazycolumn)
- [`LazyRow`](#lazyrow)
- [`LazyVerticalGrid`](#lazyverticalgrid)
- [`LazyHorizontalGrid`](#lazyhorizontalgrid)
- [`LazyVerticalStaggeredGrid`](#lazyverticalstaggeredgrid)
- [`LazyHorizontalStaggeredGrid`](#lazyhorizontalstaggeredgrid)
- [`Column`](#column)
- [`Row`](#row)
- [Accessibility](#accessibility)
- [FAQ](#faq)

#### `LazyColumn`

Find more examples in [`SimpleReorderableLazyColumnScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/SimpleReorderableLazyColumnScreen.kt), [`SimpleLongPressHandleReorderableLazyColumnScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/SimpleLongPressHandleReorderableLazyColumnScreen.kt) and [`ComplexReorderableLazyColumnScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/ComplexReorderableLazyColumnScreen.kt) in the demo app.

##### Simple Example

To use this library with [`LazyColumn`](), follow this basic structure:

```kotlin
val lazyListState = rememberLazyListState()
val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
// Update the list
}

LazyColumn(state = lazyListState) {
items(list, key = { /* item key */ }) {
ReorderableItem(reorderableLazyListState, key = /* item key */) { isDragging ->
// Item content

IconButton(
modifier = Modifier.draggableHandle(),
/* ... */
)
}
}
}

```

##### Complete Example (with haptic feedback)

> [!NOTE]
> `val view = LocalView.current` and `View.performHapticFeedback` are only available in Android. Comment out these lines if you are using this library in a multiplatform project.

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyListState = rememberLazyListState()
val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyColumn(
modifier = Modifier.fillMaxSize(),
state = lazyListState,
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
items(list, key = { it }) {
ReorderableItem(reorderableLazyListState, key = it) { isDragging ->
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)

Surface(shadowElevation = elevation) {
Row {
Text(it, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

##### Section Headers and Footers or Multiple Lists

The `from.index` and `to.index` in `onMove` are the indices of the items in the `LazyColumn`. If you have section headers or footers, you may need to adjust the indices accordingly. For example:

```kotlin
var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyListState = rememberLazyListState()
val reorderableLazyColumnState = rememberReorderableLazyListState(lazyListState) { from, to ->
list = list.toMutableList().apply {
add(to.index - 1, removeAt(from.index - 1))
}
}

LazyColumn(
state = lazyListState,
// ...
) {
item {
Text("Header")
}

items(list, key = { item -> item.id }) { item ->
ReorderableItem(reorderableLazyColumnState, item.id) {
// ...
}
}
}
```

##### Passing `Modifier.draggableHandle` to a Child Composable

Since `Modifier.draggableHandle` and `Modifier.longPressDraggableHandle` can only be used in `ReorderableCollectionItemScope`, you may need to pass `ReorderableCollectionItemScope` to a child composable. For example:

```kotlin
@Composable
fun List() {
// ...

LazyColumn(state = lazyListState) {
items(list, key = { /* item key */ }) {
ReorderableItem(reorderableLazyListState, key = /* item key */) { isDragging ->
// Item content

DragHandle(this)
}
}
}
}

@Composable
fun DragHandle(scope: ReorderableCollectionItemScope) {
IconButton(
modifier = with(scope) {
Modifier.draggableHandle()
},
/* ... */
)
}
```

##### Scroll Trigger Padding

If your [`LazyColumn`]() displays under navigation bar or notification bar, you may want to add `scrollThresholdPadding` to `rememberReorderableLazyListState` to move the scroll trigger area out from under the navigation bar or notification bar.

```kotlin
val reorderableLazyListState = rememberReorderableLazyListState(
lazyListState = lazyListState,
scrollThresholdPadding = WindowInsets.systemBars.asPaddingValues(),
) { from, to ->
...
}
```

##### Use with [`Card`]()

If you want to use the [material3's Clickable Card](), you can create a [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) and pass it to both the [`Card`]() and the `Modifier.draggableHandle` (or `Modifier.longPressDraggableHandle`), `Modifier.draggableHandle` will emit drag events to the [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) so that the [`Card`]() can respond to the drag events:

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyListState = rememberLazyListState()
val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyColumn(
modifier = Modifier.fillMaxSize(),
state = lazyListState,
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
items(list, key = { it }) { item ->
ReorderableItem(reorderableLazyListState, key = item) {
val interactionSource = remember { MutableInteractionSource() }

Card(
onClick = {},
interactionSource = interactionSource,
) {
Row {
Text(item, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
interactionSource = interactionSource,
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

#### LazyRow

See [`SimpleReorderableLazyRowScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/SimpleReorderableLazyRowScreen.kt) and [`ComplexReorderableLazyRowScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/ComplexReorderableLazyRowScreen.kt) in the demo app.

##### Simple Example

To use this library with [`LazyRow`](), follow this basic structure:

```kotlin
val lazyListState = rememberLazyListState()
val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
// Update the list
}

LazyRow(state = lazyListState) {
items(list, key = { /* item key */ }) {
ReorderableItem(reorderableLazyListState, key = /* item key */) { isDragging ->
// Item content

IconButton(
modifier = Modifier.draggableHandle(),
/* ... */
)
}
}
}

```

##### Complete Example (with haptic feedback)

> [!NOTE]
> `val view = LocalView.current` and `View.performHapticFeedback` are only available in Android. Comment out these lines if you are using this library in a multiplatform project.

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyListState = rememberLazyListState()
val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyRow(
modifier = Modifier.fillMaxSize(),
state = lazyListState,
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(list, key = { it }) {
ReorderableItem(reorderableLazyListState, key = it) { isDragging ->
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)

Surface(shadowElevation = elevation) {
Column {
Text(it, Modifier.padding(vertical = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

##### Section Headers and Footers or Multiple Lists

The `from.index` and `to.index` in `onMove` are the indices of the items in the `LazyRow`. If you have section headers or footers, you may need to adjust the indices accordingly. For example:

```kotlin
var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyListState = rememberLazyListState()
val reorderableLazyRowState = rememberReorderableLazyListState(lazyListState) { from, to ->
list = list.toMutableList().apply {
add(to.index - 1, removeAt(from.index - 1))
}
}

LazyRow(
state = lazyListState,
// ...
) {
item {
Text("Header")
}

items(list, key = { item -> item.id }) { item ->
ReorderableItem(reorderableLazyRowState, item.id) {
// ...
}
}
}
```

##### Passing `Modifier.draggableHandle` to a Child Composable

Since `Modifier.draggableHandle` and `Modifier.longPressDraggableHandle` can only be used in `ReorderableCollectionItemScope`, you may need to pass `ReorderableCollectionItemScope` to a child composable. For example:

```kotlin
@Composable
fun List() {
// ...

LazyRow(state = lazyListState) {
items(list, key = { /* item key */ }) {
ReorderableItem(reorderableLazyListState, key = /* item key */) { isDragging ->
// Item content

DragHandle(this)
}
}
}
}

@Composable
fun DragHandle(scope: ReorderableCollectionItemScope) {
IconButton(
modifier = with(scope) {
Modifier.draggableHandle()
},
/* ... */
)
}
```

##### Scroll Trigger Padding

If your [`LazyRow`]() displays under navigation bar or notification bar, you may want to add `scrollThresholdPadding` to `rememberReorderableLazyListState` to move the scroll trigger area out from under the navigation bar or notification bar.

```kotlin
val reorderableLazyListState = rememberReorderableLazyListState(
lazyListState = lazyListState,
scrollThresholdPadding = WindowInsets.systemBars.asPaddingValues(),
) { from, to ->
...
}
```

##### Use with [`Card`]()

If you want to use the [material3's Clickable Card](), you can create a [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) and pass it to both the [`Card`]() and the `Modifier.draggableHandle` (or `Modifier.longPressDraggableHandle`), `Modifier.draggableHandle` will emit drag events to the [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) so that the [`Card`]() can respond to the drag events:

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyListState = rememberLazyListState()
val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyRow(
modifier = Modifier.fillMaxSize(),
state = lazyListState,
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(list, key = { it }) { item ->
ReorderableItem(reorderableLazyListState, key = item) {
val interactionSource = remember { MutableInteractionSource() }

Card(
onClick = {},
interactionSource = interactionSource,
) {
Column {
Text(item, Modifier.padding(vertical = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
interactionSource = interactionSource,
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

#### `LazyVerticalGrid`

Find more examples in [`SimpleReorderableLazyVerticalGridScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/SimpleReorderableLazyVerticalGridScreen.kt) in the demo app.

##### Simple Example

To use this library with [`LazyVerticalGrid`](), follow this basic structure:

```kotlin
val lazyGridState = rememberLazyGridState()
val reorderableLazyGridState = rememberReorderableLazyGridState(lazyGridState) { from, to ->
// Update the list
}

LazyVerticalGrid(state = lazyGridState) {
items(list, key = { /* item key */ }) {
ReorderableItem(reorderableLazyGridState, key = /* item key */) { isDragging ->
// Item content

IconButton(
modifier = Modifier.draggableHandle(),
/* ... */
)
}
}
}

```

##### Complete Example (with haptic feedback)

> [!NOTE]
> `val view = LocalView.current` and `View.performHapticFeedback` are only available in Android. Comment out these lines if you are using this library in a multiplatform project.

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyGridState = rememberLazyGridState()
val reorderableLazyGridState = rememberReorderableLazyGridState(lazyGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 96.dp),
modifier = Modifier.fillMaxSize(),
state = lazyGridState,
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(list, key = { it }) {
ReorderableItem(reorderableLazyGridState, key = it) { isDragging ->
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)

Surface(shadowElevation = elevation) {
Row {
Text(it, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

##### Section Headers and Footers or Multiple Lists

The `from.index` and `to.index` in `onMove` are the indices of the items in the `LazyVerticalGrid`. If you have section headers or footers, you may need to adjust the indices accordingly. For example:

```kotlin
var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyGridState = rememberLazyGridState()
val reorderableLazyGridState = rememberReorderableLazyGridState(lazyGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index - 1, removeAt(from.index - 1))
}
}

LazyVerticalGrid(
state = lazyGridState,
// ...
) {
item {
Text("Header")
}

items(list, key = { item -> item.id }) { item ->
ReorderableItem(reorderableLazyGridState, item.id) {
// ...
}
}
}
```

##### Passing `Modifier.draggableHandle` to a Child Composable

Since `Modifier.draggableHandle` and `Modifier.longPressDraggableHandle` can only be used in `ReorderableCollectionItemScope`, you may need to pass `ReorderableCollectionItemScope` to a child composable. For example:

```kotlin
@Composable
fun Grid() {
// ...

LazyVerticalGrid(state = lazyGridState) {
items(Grid, key = { /* item key */ }) {
ReorderableItem(reorderableLazyGridState, key = /* item key */) { isDragging ->
// Item content

DragHandle(this)
}
}
}
}

@Composable
fun DragHandle(scope: ReorderableCollectionItemScope) {
IconButton(
modifier = with(scope) {
Modifier.draggableHandle()
},
/* ... */
)
}
```

##### Scroll Trigger Padding

If your [`LazyVerticalGrid`]() displays under navigation bar or notification bar, you may want to add `scrollThresholdPadding` to `rememberReorderableLazyGridState` to move the scroll trigger area out from under the navigation bar or notification bar.

```kotlin
val reorderableLazyGridState = rememberReorderableLazyGridState(
lazyGridState = lazyGridState,
scrollThresholdPadding = WindowInsets.systemBars.asPaddingValues(),
) { from, to ->
...
}
```

##### Use with [`Card`]()

If you want to use the [material3's Clickable Card](), you can create a [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) and pass it to both the [`Card`]() and the `Modifier.draggableHandle` (or `Modifier.longPressDraggableHandle`), `Modifier.draggableHandle` will emit drag events to the [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) so that the [`Card`]() can respond to the drag events:

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyGridState = rememberLazyGridState()
val reorderableLazyGridState = rememberReorderableLazyGridState(lazyGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 96.dp),
modifier = Modifier.fillMaxSize(),
state = lazyGridState,
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(list, key = { it }) { item ->
ReorderableItem(reorderableLazyGridState, key = item) {
val interactionSource = remember { MutableInteractionSource() }

Card(
onClick = {},
interactionSource = interactionSource,
) {
Row {
Text(item, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
interactionSource = interactionSource,
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

#### `LazyHorizontalGrid`

Find more examples in [`SimpleReorderableLazyHorizontalGridScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/SimpleReorderableLazyHorizontalGridScreen.kt) in the demo app.

##### Simple Example

To use this library with [`LazyHorizontalGrid`](), follow this basic structure:

```kotlin
val lazyGridState = rememberLazyGridState()
val reorderableLazyGridState = rememberReorderableLazyGridState(lazyGridState) { from, to ->
// Update the list
}

LazyHorizontalGrid(state = lazyGridState) {
items(list, key = { /* item key */ }) {
ReorderableItem(reorderableLazyGridState, key = /* item key */) { isDragging ->
// Item content

IconButton(
modifier = Modifier.draggableHandle(),
/* ... */
)
}
}
}

```

##### Complete Example (with haptic feedback)

> [!NOTE]
> `val view = LocalView.current` and `View.performHapticFeedback` are only available in Android. Comment out these lines if you are using this library in a multiplatform project.

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyGridState = rememberLazyGridState()
val reorderableLazyGridState = rememberReorderableLazyGridState(lazyGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyHorizontalGrid(
rows = GridCells.Adaptive(minSize = 96.dp),
modifier = Modifier.fillMaxSize(),
state = lazyGridState,
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(list, key = { it }) {
ReorderableItem(reorderableLazyGridState, key = it) { isDragging ->
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)

Surface(shadowElevation = elevation) {
Row {
Text(it, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

##### Section Headers and Footers or Multiple Lists

The `from.index` and `to.index` in `onMove` are the indices of the items in the `LazyHorizontalGrid`. If you have section headers or footers, you may need to adjust the indices accordingly. For example:

```kotlin
var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyGridState = rememberLazyGridState()
val reorderableLazyGridState = rememberReorderableLazyGridState(lazyGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index - 1, removeAt(from.index - 1))
}
}

LazyHorizontalGrid(
state = lazyGridState,
// ...
) {
item {
Text("Header")
}

items(list, key = { item -> item.id }) { item ->
ReorderableItem(reorderableLazyGridState, item.id) {
// ...
}
}
}
```

##### Passing `Modifier.draggableHandle` to a Child Composable

Since `Modifier.draggableHandle` and `Modifier.longPressDraggableHandle` can only be used in `ReorderableCollectionItemScope`, you may need to pass `ReorderableCollectionItemScope` to a child composable. For example:

```kotlin
@Composable
fun Grid() {
// ...

LazyHorizontalGrid(state = lazyGridState) {
items(Grid, key = { /* item key */ }) {
ReorderableItem(reorderableLazyGridState, key = /* item key */) { isDragging ->
// Item content

DragHandle(this)
}
}
}
}

@Composable
fun DragHandle(scope: ReorderableCollectionItemScope) {
IconButton(
modifier = with(scope) {
Modifier.draggableHandle()
},
/* ... */
)
}
```

##### Scroll Trigger Padding

If your [`LazyHorizontalGrid`]() displays under navigation bar or notification bar, you may want to add `scrollThresholdPadding` to `rememberReorderableLazyGridState` to move the scroll trigger area out from under the navigation bar or notification bar.

```kotlin
val reorderableLazyGridState = rememberReorderableLazyGridState(
lazyGridState = lazyGridState,
scrollThresholdPadding = WindowInsets.systemBars.asPaddingValues(),
) { from, to ->
...
}
```

##### Use with [`Card`]()

If you want to use the [material3's Clickable Card](), you can create a [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) and pass it to both the [`Card`]() and the `Modifier.draggableHandle` (or `Modifier.longPressDraggableHandle`), `Modifier.draggableHandle` will emit drag events to the [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) so that the [`Card`]() can respond to the drag events:

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyGridState = rememberLazyGridState()
val reorderableLazyGridState = rememberReorderableLazyGridState(lazyGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyHorizontalGrid(
rows = GridCells.Adaptive(minSize = 96.dp),
modifier = Modifier.fillMaxSize(),
state = lazyGridState,
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(list, key = { it }) { item ->
ReorderableItem(reorderableLazyGridState, key = item) {
val interactionSource = remember { MutableInteractionSource() }

Card(
onClick = {},
interactionSource = interactionSource,
) {
Row {
Text(item, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
interactionSource = interactionSource,
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

#### `LazyVerticalStaggeredGrid`

Find more examples in [`SimpleReorderableLazyVerticalStaggeredGridScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/SimpleReorderableLazyVerticalStaggeredGridScreen.kt) in the demo app.

##### Simple Example

To use this library with [`LazyVerticalStaggeredGrid`](), follow this basic structure:

```kotlin
val lazyStaggeredGridState = rememberLazyStaggeredGridState()
val reorderableLazyStaggeredGridState = rememberReorderableLazyStaggeredGridState(lazyStaggeredGridState) { from, to ->
// Update the list
}

LazyVerticalStaggeredGrid(state = lazyStaggeredGridState) {
items(list, key = { /* item key */ }) {
ReorderableItem(reorderableLazyStaggeredGridState, key = /* item key */) { isDragging ->
// Item content

IconButton(
modifier = Modifier.draggableHandle(),
/* ... */
)
}
}
}

```

##### Complete Example (with haptic feedback)

> [!NOTE]
> `val view = LocalView.current` and `View.performHapticFeedback` are only available in Android. Comment out these lines if you are using this library in a multiplatform project.

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyStaggeredGridState = rememberLazyStaggeredGridState()
val reorderableLazyStaggeredGridState = rememberReorderableLazyStaggeredGridState(lazyStaggeredGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(minSize = 96.dp),
modifier = Modifier.fillMaxSize(),
state = lazyStaggeredGridState,
contentPadding = PaddingValues(8.dp),
verticalItemSpacing = 8.dp,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(list, key = { it }) {
ReorderableItem(reorderableLazyStaggeredGridState, key = it) { isDragging ->
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)

Surface(shadowElevation = elevation) {
Row {
Text(it, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

##### Section Headers and Footers or Multiple Lists

The `from.index` and `to.index` in `onMove` are the indices of the items in the `LazyVerticalStaggeredGrid`. If you have section headers or footers, you may need to adjust the indices accordingly. For example:

```kotlin
var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyStaggeredGridState = rememberLazyStaggeredGridState()
val reorderableLazyStaggeredGridState = rememberReorderableLazyStaggeredGridState(lazyStaggeredGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index - 1, removeAt(from.index - 1))
}
}

LazyVerticalStaggeredGrid(
state = lazyStaggeredGridState,
// ...
) {
item {
Text("Header")
}

items(list, key = { item -> item.id }) { item ->
ReorderableItem(reorderableLazyStaggeredGridState, item.id) {
// ...
}
}
}
```

##### Passing `Modifier.draggableHandle` to a Child Composable

Since `Modifier.draggableHandle` and `Modifier.longPressDraggableHandle` can only be used in `ReorderableCollectionItemScope`, you may need to pass `ReorderableCollectionItemScope` to a child composable. For example:

```kotlin
@Composable
fun Grid() {
// ...

LazyVerticalStaggeredGrid(state = lazyStaggeredGridState) {
items(list, key = { /* item key */ }) {
ReorderableItem(reorderableLazyStaggeredGridState, key = /* item key */) { isDragging ->
// Item content

DragHandle(this)
}
}
}
}

@Composable
fun DragHandle(scope: ReorderableCollectionItemScope) {
IconButton(
modifier = with(scope) {
Modifier.draggableHandle()
},
/* ... */
)
}
```

##### Scroll Trigger Padding

If your [`LazyVerticalStaggeredGrid`]() displays under navigation bar or notification bar, you may want to add `scrollThresholdPadding` to `rememberReorderableLazyStaggeredGridState` to move the scroll trigger area out from under the navigation bar or notification bar.

```kotlin
val reorderableLazyStaggeredGridState = rememberReorderableLazyStaggeredGridState(
lazyStaggeredGridState = lazyStaggeredGridState,
scrollThresholdPadding = WindowInsets.systemBars.asPaddingValues(),
) { from, to ->
...
}
```

##### Use with [`Card`]()

If you want to use the [material3's Clickable Card](), you can create a [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) and pass it to both the [`Card`]() and the `Modifier.draggableHandle` (or `Modifier.longPressDraggableHandle`), `Modifier.draggableHandle` will emit drag events to the [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) so that the [`Card`]() can respond to the drag events:

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyStaggeredGridState = rememberLazyStaggeredGridState()
val reorderableLazyStaggeredGridState = rememberReorderableLazyStaggeredGridState(lazyStaggeredGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(minSize = 96.dp),
modifier = Modifier.fillMaxSize(),
state = lazyStaggeredGridState,
contentPadding = PaddingValues(8.dp),
verticalItemSpacing = 8.dp,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(list, key = { it }) { item ->
ReorderableItem(reorderableLazyStaggeredGridState, key = item) {
val interactionSource = remember { MutableInteractionSource() }

Card(
onClick = {},
interactionSource = interactionSource,
) {
Row {
Text(item, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
interactionSource = interactionSource,
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

#### `LazyHorizontalStaggeredGrid`

Find more examples in [`SimpleReorderableLazyHorizontalStaggeredGridScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/SimpleReorderableLazyHorizontalStaggeredGridScreen.kt) in the demo app.

##### Simple Example

To use this library with [`LazyHorizontalStaggeredGrid`](), follow this basic structure:

```kotlin
val lazyStaggeredGridState = rememberLazyStaggeredGridState()
val reorderableLazyStaggeredGridState = rememberReorderableLazyStaggeredGridState(lazyStaggeredGridState) { from, to ->
// Update the list
}

LazyHorizontalStaggeredGrid(state = lazyStaggeredGridState) {
items(list, key = { /* item key */ }) {
ReorderableItem(reorderableLazyStaggeredGridState, key = /* item key */) { isDragging ->
// Item content

IconButton(
modifier = Modifier.draggableHandle(),
/* ... */
)
}
}
}

```

##### Complete Example (with haptic feedback)

> [!NOTE]
> `val view = LocalView.current` and `View.performHapticFeedback` are only available in Android. Comment out these lines if you are using this library in a multiplatform project.

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyStaggeredGridState = rememberLazyStaggeredGridState()
val reorderableLazyStaggeredGridState = rememberReorderableLazyStaggeredGridState(lazyStaggeredGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyHorizontalStaggeredGrid(
rows = StaggeredGridCells.Adaptive(minSize = 96.dp),
modifier = Modifier.fillMaxSize(),
state = lazyStaggeredGridState,
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalItemSpacing = 8.dp,
) {
items(list, key = { it }) {
ReorderableItem(reorderableLazyStaggeredGridState, key = it) { isDragging ->
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)

Surface(shadowElevation = elevation) {
Row {
Text(it, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

##### Section Headers and Footers or Multiple Lists

The `from.index` and `to.index` in `onMove` are the indices of the items in the `LazyHorizontalStaggeredGrid`. If you have section headers or footers, you may need to adjust the indices accordingly. For example:

```kotlin
var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyStaggeredGridState = rememberLazyStaggeredGridState()
val reorderableLazyStaggeredGridState = rememberReorderableLazyStaggeredGridState(lazyStaggeredGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index - 1, removeAt(from.index - 1))
}
}

LazyHorizontalStaggeredGrid(
state = lazyStaggeredGridState,
// ...
) {
item {
Text("Header")
}

items(list, key = { item -> item.id }) { item ->
ReorderableItem(reorderableLazyStaggeredGridState, item.id) {
// ...
}
}
}
```

##### Passing `Modifier.draggableHandle` to a Child Composable

Since `Modifier.draggableHandle` and `Modifier.longPressDraggableHandle` can only be used in `ReorderableCollectionItemScope`, you may need to pass `ReorderableCollectionItemScope` to a child composable. For example:

```kotlin
@Composable
fun Grid() {
// ...

LazyHorizontalStaggeredGrid(state = lazyStaggeredGridState) {
items(list, key = { /* item key */ }) {
ReorderableItem(reorderableLazyStaggeredGridState, key = /* item key */) { isDragging ->
// Item content

DragHandle(this)
}
}
}
}

@Composable
fun DragHandle(scope: ReorderableCollectionItemScope) {
IconButton(
modifier = with(scope) {
Modifier.draggableHandle()
},
/* ... */
)
}
```

##### Scroll Trigger Padding

If your [`LazyHorizontalStaggeredGrid`]() displays under navigation bar or notification bar, you may want to add `scrollThresholdPadding` to `rememberReorderableLazyStaggeredGridState` to move the scroll trigger area out from under the navigation bar or notification bar.

```kotlin
val reorderableLazyStaggeredGridState = rememberReorderableLazyStaggeredGridState(
lazyStaggeredGridState = lazyStaggeredGridState,
scrollThresholdPadding = WindowInsets.systemBars.asPaddingValues(),
) { from, to ->
...
}
```

##### Use with [`Card`]()

If you want to use the [material3's Clickable Card](), you can create a [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) and pass it to both the [`Card`]() and the `Modifier.draggableHandle` (or `Modifier.longPressDraggableHandle`), `Modifier.draggableHandle` will emit drag events to the [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) so that the [`Card`]() can respond to the drag events:

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyStaggeredGridState = rememberLazyStaggeredGridState()
val reorderableLazyStaggeredGridState = rememberReorderableLazyStaggeredGridState(lazyStaggeredGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}

ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
}

LazyHorizontalStaggeredGrid(
rows = StaggeredGridCells.Adaptive(minSize = 96.dp),
modifier = Modifier.fillMaxSize(),
state = lazyStaggeredGridState,
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalItemSpacing = 8.dp,
) {
items(list, key = { it }) { item ->
ReorderableItem(reorderableLazyStaggeredGridState, key = item) {
val interactionSource = remember { MutableInteractionSource() }

Card(
onClick = {},
interactionSource = interactionSource,
) {
Row {
Text(item, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
interactionSource = interactionSource,
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
```

#### `Column`

Find more examples in [`ReorderableColumnScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/ReorderableColumnScreen.kt) and [`LongPressHandleReorderableColumnScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/LongPressHandleReorderableColumnScreen.kt) in the demo app.

##### Simple Example

To use this library with [`Column`](), follow this basic structure:

```kotlin
ReorderableColumn(
list = list,
onSettle = { fromIndex, toIndex ->
// Update the list
},
) { index, item, isDragging ->
key(item.id) {
// Item content

IconButton(modifier = Modifier.draggableHandle(), /* ... */)
}
}
```

##### Complete Example (with haptic feedback)

> [!NOTE]
> `val view = LocalView.current` and `View.performHapticFeedback` are only available in Android. Comment out these lines if you are using this library in a multiplatform project.

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(4) { "Item $it" }) }

ReorderableColumn(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
list = list,
onSettle = { fromIndex, toIndex ->
list = list.toMutableList().apply {
add(toIndex, removeAt(fromIndex))
}
},
onMove = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
},
verticalArrangement = Arrangement.spacedBy(8.dp),
) { _, item, isDragging ->
key(item) {
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)

Surface(shadowElevation = elevation) {
Row {
Text(item, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
```

##### Passing `Modifier.draggableHandle` to a Child Composable

Since `Modifier.draggableHandle` and `Modifier.longPressDraggableHandle` can only be used in `ReorderableScope`, you may need to pass `ReorderableScope` to a child composable. For example:

```kotlin
@Composable
fun List() {
// ...

ReorderableColumn(
list = list,
onSettle = { fromIndex, toIndex ->
// Update the list
},
) { index, item, isDragging ->
key(item.id) {
// Item content

DragHandle(this)
}
}
}

@Composable
fun DragHandle(scope: ReorderableScope) {
IconButton(modifier = with(scope) { Modifier.draggableHandle() }, /* ... */)
}
```

##### Use with [`Card`]()

If you want to use the [material3's Clickable Card](), you can create a [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) and pass it to both the [`Card`]() and the `Modifier.draggableHandle` (or `Modifier.longPressDraggableHandle`), `Modifier.draggableHandle` will emit drag events to the [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) so that the [`Card`]() can respond to the drag events:

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(4) { "Item $it" }) }

ReorderableColumn(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
list = list,
onSettle = { fromIndex, toIndex ->
list = list.toMutableList().apply {
add(toIndex, removeAt(fromIndex))
}
},
onMove = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
},
verticalArrangement = Arrangement.spacedBy(8.dp),
) { _, item, _ ->
key(item) {
val interactionSource = remember { MutableInteractionSource() }

Card(
onClick = {},
interactionSource = interactionSource,
) {
Row {
Text(item, Modifier.padding(horizontal = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
interactionSource = interactionSource,
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
```

#### `Row`

See [`ReorderableRowScreen.kt`](demoApp/composeApp/src/commonMain/kotlin/sh/calvin/reorderable/demo/ui/ReorderableRowScreen.kt) in the demo app.

##### Simple Example

To use this library with [`Row`](), follow this basic structure:

```kotlin
ReorderableRow(
list = list,
onSettle = { fromIndex, toIndex ->
// Update the list
},
) { index, item, isDragging ->
key(item.id) {
// Item content

IconButton(modifier = Modifier.draggableHandle(), /* ... */)
}
}
```

##### Complete Example (with haptic feedback)

> [!NOTE]
> `val view = LocalView.current` and `View.performHapticFeedback` are only available in Android. Comment out these lines if you are using this library in a multiplatform project.

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(4) { "Item $it" }) }

ReorderableRow(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
list = list,
onSettle = { fromIndex, toIndex ->
list = list.toMutableList().apply {
add(toIndex, removeAt(fromIndex))
}
},
onMove = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
},
verticalArrangement = Arrangement.spacedBy(8.dp),
) { _, item, isDragging ->
key(item) {
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)

Surface(shadowElevation = elevation) {
Column {
Text(item, Modifier.padding(vertical = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
```

##### Passing `Modifier.draggableHandle` to a Child Composable

Since `Modifier.draggableHandle` and `Modifier.longPressDraggableHandle` can only be used in `ReorderableScope`, you may need to pass `ReorderableScope` to a child composable. For example:

```kotlin
@Composable
fun List() {
// ...

ReorderableRow(
list = list,
onSettle = { fromIndex, toIndex ->
// Update the list
},
) { index, item, isDragging ->
key(item.id) {
// Item content

DragHandle(this)
}
}
}

@Composable
fun DragHandle(scope: ReorderableScope) {
IconButton(modifier = with(scope) { Modifier.draggableHandle() }, /* ... */)
}
```

##### Use with [`Card`]()

If you want to use the [material3's Clickable Card](), you can create a [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) and pass it to both the [`Card`]() and the `Modifier.draggableHandle` (or `Modifier.longPressDraggableHandle`), `Modifier.draggableHandle` will emit drag events to the [`MutableInteractionSource`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/interaction/MutableInteractionSource) so that the [`Card`]() can respond to the drag events:

```kotlin
val view = LocalView.current

var list by remember { mutableStateOf(List(4) { "Item $it" }) }

ReorderableRow(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
list = list,
onSettle = { fromIndex, toIndex ->
list = list.toMutableList().apply {
add(toIndex, removeAt(fromIndex))
}
},
onMove = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK
)
},
verticalArrangement = Arrangement.spacedBy(8.dp),
) { _, item, _ ->
key(item) {
val interactionSource = remember { MutableInteractionSource() }

Card(
onClick = {},
interactionSource = interactionSource,
) {
Column {
Text(item, Modifier.padding(vertical = 8.dp))
IconButton(
modifier = Modifier.draggableHandle(
onDragStarted = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_START
)
},
onDragStopped = {
ViewCompat.performHapticFeedback(
view,
HapticFeedbackConstantsCompat.GESTURE_END
)
},
interactionSource = interactionSource,
),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
```

#### Accessibility

See the demo app for examples of how to make the reorderable list accessible.

If the items in the list do not contain any button besides the drag handle, I recommend adding "Move Up"/"Move Down"/"Move Left"/"Move Right" actions to the TalkBack menu in each item via [`SemanticsPropertyReceiver.customActions`]() and applying [`Modifier.clearAndSetSemantics`]() to the drag handle button to make the drag handle button not focusable for TalkBack. For more information, see [Key steps to improve Compose accessibility](https://developer.android.com/develop/ui/compose/accessibility/key-steps#custom-actions).

#### FAQ

##### When `onMove` is called to move items, the dragging item flickers/jumps/flashes.

> [!NOTE]
> This assumes you're using version 2.0.3 or later of this library.

The `onMove` function expects the list to be updated before it returns. If the list is updated after `onMove` returns, the dragging item will flicker. To fix this, update the list before returning from `onMove`.

```kotlin
val reorderableLazyXXXXState = rememberReorderableLazyXXXXState(listState) { from, to ->
// do NOT wrap the updateList call in `launch`
updateList(from, to)
}

suspend fun updateList(from: Int, to: Int) {
// long update operation
}
```

If you can't keep the list update inside `onMove`, you can use a channel to communicate between `onMove` and the list update composition. Here's an example:

```kotlin
val listUpdatedChannel = remember { Channel() }
val reorderableLazyXXXXState = rememberReorderableLazyXXXXState(listState) { from, to ->
// clear the channel
listUpdatedChannel.tryReceive()

// update the list

// wait for the list to be updated
listUpdatedChannel.receive()
}

LaunchedEffect(list) {
// notify the list is updated
listUpdatedChannel.trySend(Unit)
}
```

## API

### [`LazyColumn`]() / [`LazyRow`]()

- [`rememberReorderableLazyListState`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyList.kt)
- [`ReorderableItem`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyList.kt)
- [`Modifier.draggableHandle`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyCollection.kt)
- [`Modifier.longPressDraggableHandle`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyCollection.kt)

### [`LazyVerticalGrid`]() / [`LazyHorizontalGrid`]()

- [`rememberReorderableLazyGridState`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyGrid.kt)
- [`ReorderableItem`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyGrid.kt)
- [`Modifier.draggableHandle`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyCollection.kt)
- [`Modifier.longPressDraggableHandle`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyCollection.kt)

### [`LazyVerticalStaggeredGrid`]() / [`LazyHorizontalStaggeredGrid`]()

- [`rememberReorderableLazyStaggeredGridState`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyStaggeredGrid.kt)
- [`ReorderableItem`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyStaggeredGrid.kt)
- [`Modifier.draggableHandle`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyCollection.kt)
- [`Modifier.longPressDraggableHandle`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/ReorderableLazyCollection.kt)

### [`Column`]() / [`Row`]()

- [`ReorderableColumn`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/demo/ReorderableList.kt)
- [`ReorderableRow`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/demo/ReorderableList.kt)
- [`Modifier.draggableHandle`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/demo/ReorderableList.kt)
- [`Modifier.longPressDraggableHandle`](reorderable/src/commonMain/kotlin/sh/calvin/reorderable/demo/ReorderableList.kt)

## Running the demo app

To run the Android demo app, open the project in Android Studio and run the app.

To run the iOS demo app, open the iosApp project i