Reordering Beyond List - SwiftUI iOS 27

June 27, 2026

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:

  • Build your own drag-and-drop implementation using onDrag and onDrop
  • Reach for third-party packages that attempted to mimic List's editing behavior

While 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:

  • LazyVStack
  • LazyHStack
  • LazyVGrid
  • LazyHGrid
  • VStack
  • HStack
  • Custom layouts built with the Layout protocol

SwiftUI provides two new modifiers:

.reorderable()

.reorderContainer(for:)

The result is a consistent drag-to-reorder experience without implementing your own drag-and-drop system.


Why This Matters

Lazy layouts have become the preferred building blocks for many modern interfaces.

Examples include:

  • Rearranging favorite contacts
  • Organizing dashboard widgets
  • Prioritizing tasks in a kanban board
  • Reordering shortcuts
  • Building customizable home screens

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.


Understanding the Two Modifiers

SwiftUI separates reordering into two responsibilities.

Making Items Reorderable

The first modifier is applied directly to the ForEach.

.reorderable()

This tells SwiftUI that items produced by this collection can participate in reordering.

Making the Parent Container Accept 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.


Building a Reorderable LazyVStack

Suppose we're building a task management application where users can prioritize their work by dragging tasks into a different order.

Model

Our model only needs to conform to Identifiable.

struct Task: Identifiable {
    let id = UUID()
    var title: String
}

State

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")
]

Making a LazyVStack Reorderable

In 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.

Understanding ReorderDifference

The 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:

  • Updating an ordering column in a database
  • Sending a reorder request to a backend API
  • Persisting positions in user defaults
  • Synchronizing changes through a cloud service

This separation keeps SwiftUI responsible for user interaction while allowing your model layer to remain the source of truth.

Applying the Difference

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.position

The destination can be either:

.before(id)

or

.end

If the position is .before(id), we locate the index of that item.

destination = index

If SwiftUI reports .end, the item should simply be appended.

destination = endIndex

3. 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.


Reordering Items in a Grid

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.


Supporting Custom Layouts

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.


Things to Keep in Mind

Items Should Have Stable Identity

SwiftUI uses item identity to track movement.

Using UUID values or persistent database identifiers is recommended.

struct Item: Identifiable {
    let id: UUID
}

Apply Modifiers in the Correct Place

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.

Reordering Isn't Limited to Lazy Containers

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.


A Better Alternative to List?

Not always.

List still provides capabilities that stacks and grids do not.

These include:

  • Swipe actions
  • Section indexing
  • Automatic edit mode integration
  • Platform-specific styling

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.


Final Thoughts

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.