Overlapping Views in SwiftUI with zIndex

June 20th, 2025

⏱ Reading Time: 4 mins

SwiftUI lays out our views automatically based on the container they are in and the modifiers applied on them. Most of the time that’s enough, but when it comes to overlapping and the order in the Z-axis, then we have an additional tool at our disposal; the zIndex view modifier. Using it is pretty straightforward, so let’s get to know it next.

Changing view order with zIndex

By default, all views exist in the same depth level on the Z-axis. But sometimes it’s necessary to change that, and bring views in front or send them to the back of other views, and the zIndex is here for this purpose exactly.

zIndex takes a number as argument that specifies the depth level. Usually we provide an integer, but for fine-grained situations it can also get a double value. The default value assigned by the system is 0 (zero) to all views.

To bring a view on top of other views and overlap them, zIndex must get a higher value than 0, or than any other current highest value. To push a view to the back, assign it the smallest zIndex value among all. Note that we can provide negative values too. There’s no restriction to keep them higher than or equal to 0.

Here’s an example that lays out three Text views in a ZStack by overlapping them with different zIndex values. For visual clarity, they all have a specific frame and a background color. The offset is also intentional for visual separation of the views:

We can change the way views are overlapped by changing the zIndex value. Stripping all unnecessary modifiers, let’s update them like so:

This time, the “Back” Text view is in front of all others, while the “Front” has gone to the back.

Here’s a different combination:

Important notes about zIndex

The above examples show that using zIndex is not difficult, but here are some clarifications so we avoid any misunderstanding or doubts:

  • In the previous example I used the values -1, 0, and 1 for the zIndex modifier. It’s probably needless to say, but we can use any numerical value, not just these. I just chose them for the given example.
  • The zIndex value can be either hardcoded as demonstrated here, or set programmatically. For instance, suppose we’re building a card game where cards are overlapping. Apparently, it would be necessary in this case to update the zIndex programmatically on the fly.
  • In a less obvious, yet extremely important detail, note that all views should belong to the same container view. In the previous example, they are all children of the ZStack container. Don’t expect zIndex to work for views in different containers.

Using zIndex in VStack and HStack

Usually zIndex is meaningful in ZStack containers where views are laid out on top of each other by default. However, this does not prevent us from using it in the other two containers too.

First of all, it’s important to note that since both HStack and VStack lay out child views one after another in the respective axis, using offset or spacing appropriately in order to overlap views is necessary. You’ll see that in the following example, where the negative spacing in the VStack makes views overlap each other. Then, using zIndex, we bring the middle one in front of the other two:

In a similar way we handle zIndex in views in an HStack:

Wrapping Up

Even though the zIndex modifier might not be commonly used, it becomes really useful when we need to change the order in which views overlap. In this post you’ve read what you need to know, so just go ahead and use zIndex the way it suits you the most. Thanks for reading!

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.