For years, List was the only SwiftUI container that offered a polished built-in item reordering experience.
If you wanted users to rearrange items inside a LazyVStack, LazyVGrid, or a custom layout, you typically had two options:
onDrag and onDropWhile these approaches worked, they often required significant bookkeeping. Developers had to track drag state, determine insertion positions, animate movement, and keep their data source synchronized.
In iOS 27, SwiftUI introduces a much simpler approach.
You can now make items reorderable in any SwiftUI container, including:
LazyVStackLazyHStackLazyVGridLazyHGridVStackHStackLayout protocolSwiftUI provides two new modifiers:
.reorderable()
.reorderContainer(for:)The result is a consistent drag-to-reorder experience without implementing your own drag-and-drop system.
Lazy layouts have become the preferred building blocks for many modern interfaces.
Examples include:
Until iOS 27, these interfaces required custom interaction code.
With the new APIs, reordering behaves much closer to how List has always worked, but without forcing developers to adopt a list-based presentation.
SwiftUI separates reordering into two responsibilities.
The first modifier is applied directly to the ForEach.
.reorderable()This tells SwiftUI that items produced by this collection can participate in reordering.
The second modifier belongs on the layout container.
.reorderContainer(for: Item.self)This enables the surrounding layout to manage drag interactions and update positions.
Think of it as establishing a reordering context.
Without it, the container behaves like a normal stack or grid.
LazyVStackSuppose we're building a task management application where users can prioritize their work by dragging tasks into a different order.
Our model only needs to conform to Identifiable.
struct Task: Identifiable {
let id = UUID()
var title: String
}We'll keep the tasks inside a local state variable.
@State private var tasks = [
Task(title: "Design Invoice"),
Task(title: "Send Proposal"),
Task(title: "Review Feedback"),
Task(title: "Publish Update")
]LazyVStack ReorderableIn iOS 27, there are two pieces required to enable reordering.
First, mark the ForEach as reorderable.
Second, attach a reorder container to the parent layout and tell SwiftUI how changes should be applied to your data source.
struct ContentView: View {
@State private var tasks = [
Task(title: "Design Invoice"),
Task(title: "Send Proposal"),
Task(title: "Review Feedback"),
Task(title: "Publish Update")
]
var body: some View {
ScrollView {
LazyVStack(spacing: 12) {
ForEach(tasks) { task in
Text(task.title)
.frame(maxWidth: .infinity)
.padding()
.background(.blue.opacity(0.1))
.clipShape(.rect(cornerRadius: 12))
}
.reorderable()
}
.padding()
.reorderContainer(for: Task.self) { difference in
tasks.apply(difference: difference)
}
}
}
}Once these two modifiers are added, users can press and drag items to rearrange them within the stack.
Unlike List, SwiftUI does not directly mutate your collection. Instead, it reports a ReorderDifference describing what moved and where it should be inserted.
It is your responsibility to update the backing collection.
ReorderDifferenceThe closure passed to reorderContainer(for:) receives a ReorderDifference.
.reorderContainer(for: Task.self) { difference in
tasks.apply(difference: difference)
}This object represents a move operation rather than an entirely new array ordering.
Conceptually, SwiftUI is saying:
"The item with this identifier moved from here to there."
Your application can then decide how to persist that change.
For simple in-memory collections, that might mean updating an array.
For more advanced applications, it could involve:
This separation keeps SwiftUI responsible for user interaction while allowing your model layer to remain the source of truth.
One possible implementation looks like this.
extension Array {
mutating func apply<CollectionID: Hashable & Sendable>(
difference: ReorderDifference<Element.ID, CollectionID>
) where Element: Identifiable, Element.ID: Sendable {
// Find the source element that moved.
guard let sourceIndex = firstIndex(
where: { $0.id == difference.sources[0] }
) else { return }
let movedElement = remove(at: sourceIndex)
// Find the destination of that element.
var destination: Int
switch difference.destination.position {
case let .before(value):
guard let index = firstIndex(
where: { $0.id == value }
) else { return }
destination = index
case .end:
destination = endIndex
}
insert(movedElement, at: destination)
}
}This helper function on Array performs three steps:
1. Locate the moved item
SwiftUI provides the identifiers of the source elements through difference.sources.
difference.sources[0]We search our array for the matching item.
let movedElement = remove(at: sourceIndex)Removing it temporarily gives us an array representing the state before insertion.
2. Determine the insertion position
ReorderDifference describes destinations using positions.
difference.destination.positionThe destination can be either:
.before(id)or
.endIf the position is .before(id), we locate the index of that item.
destination = indexIf SwiftUI reports .end, the item should simply be appended.
destination = endIndex3. Insert the element
Finally, we insert the moved item back into the collection.
insert(movedElement, at: destination)At this point, tasks reflects the new order chosen by the user.
The exact same APIs work for grids.
LazyVGrid(
columns: [
GridItem(.adaptive(minimum: 100))
]
) {
ForEach(photos) { photo in
PhotoCell(photo)
}
.reorderable()
}
.reorderContainer(for: Photo.self) { difference in
photos.apply(difference: difference)
}SwiftUI automatically animates neighboring cells as items move through the grid.
This makes the API particularly useful for photo pickers, widget organizers, and dashboard interfaces.
One of the most interesting aspects of this feature is that it isn't limited to Apple's built-in containers.
Custom layouts created with the Layout protocol can also participate in reordering.
Imagine implementing a Pinterest-style masonry layout.
Prior to iOS 27, supporting drag-and-drop reordering would require custom hit testing and insertion calculations.
With the new APIs, users gain the same interaction model available in stacks and grids while allowing your custom layout to continue controlling placement.
SwiftUI uses item identity to track movement.
Using UUID values or persistent database identifiers is recommended.
struct Item: Identifiable {
let id: UUID
}reorderable() belongs on the ForEach.
ForEach(items) { item in
ItemView(item)
}
.reorderable()reorderContainer(for:) belongs on the parent layout.
LazyVStack {
}
.reorderContainer(for: Item.self) { difference in
items.apply(difference: difference)
}Placing these modifiers elsewhere prevents SwiftUI from establishing the reordering relationship.
Although introduced as part of lazy layout enhancements, these APIs also work with ordinary stacks.
VStack {
ForEach(items) { item in
ItemView(item)
}
.reorderable()
}
.reorderContainer(for: Item.self) { difference in
items.apply(difference: difference)
}This is useful when virtualization is unnecessary and the number of items is relatively small.
Not always.
List still provides capabilities that stacks and grids do not.
These include:
If your interface already fits naturally into a List, continuing to use List remains a good choice.
However, if you've ever implemented dozens of lines of drag-and-drop code just to let users rearrange cards inside a LazyVStack, iOS 27 finally provides a native solution.
I particularly like Apple's decision to expose reorder operations through ReorderDifference instead of directly mutating collections.
At first glance, an API such as:
.reorderable(tasks)might seem simpler.
However, ReorderDifference scales much better for real-world applications.
Tasks stored in a database can update only affected records.
Cloud-backed collections can synchronize lightweight reorder operations.
Backend services can receive move requests instead of entire reordered arrays.
SwiftUI computes the movement, while your application remains responsible for deciding how that movement should be persisted.
That separation feels very consistent with SwiftUI's data-driven philosophy.
And perhaps that's the biggest takeaway from these APIs.
Reordering is no longer a feature exclusive to List.
In iOS 27, it becomes a capability of SwiftUI layouts themselves.
Personally, I think this is one of the most underrated SwiftUI additions announced at WWDC 2026. Many applications have implemented custom drag-and-drop behavior for years just to support rearranging cards, shortcuts, dashboards, or widgets. Being able to delete all of that infrastructure and replace it with two modifiers feels like a meaningful step forward for SwiftUI.
If you have suggestions or opinion, feel free to connect with me on X and send me a DM. If this article helped you, Buy me a coffee.