SwiftUI provides three basic containers for arranging views: HStack, VStack, and ZStack. These stacks are used for creating simple and complex user interfaces. Let’s see how they work.
VStack: Arranging Views Vertically
A VStack (Vertical Stack) arranges its child views in a vertical line, one on top of the other.
Example:
VStack {
Text("Top Item")
Text("Middle Item")
Text("Bottom Item")
}
This will display three text views stacked vertically.

By default, views within a VStack are centered horizontally. You can control the alignment using the alignment parameter:
VStack(alignment: .leading) {
Text("Aligned to Leading Edge")
Text("This too")
}

You can also add spacing between items:
VStack(spacing: 20) {
Text("Item 1")
Text("Item 2") // There will be 20 points of space above this text
}

HStack: Arranging Views Horizontally
An HStack (Horizontal Stack) arranges its child views in a horizontal line, side by side.
Example:
HStack {
Text("Left Item")
Text("Center Item")
Text("Right Item")
}

This will display three text views arranged horizontally.
Similar to VStack, HStack also has an alignment parameter (for vertical alignment within the HStack) and a spacing parameter.
HStack(alignment: .top, spacing: 15) {
Image(systemName: "star.fill")
Text("Favorite")
}
In this example, the star image and the “Favorite” text will be aligned to their top edges with 15 points of space between them.

ZStack: Layering Views
A ZStack (Depth Stack) overlays its child views, arranging them on top of each other along the z-axis (depth). The first child view in the ZStack is at the bottom, and subsequent views are layered on top.
Example:
ZStack {
Color.blue // Bottom layer
Text("Text on top of color") // Top layer
}
This will display blue background color with the text “Text on top of color” rendered over it.

Common Use Cases:
- Placing text or controls over an image or background color.
- Creating custom UI elements that require overlapping views (e.g., badges on icons).
- Implementing overlay views like loading indicators or pop-up messages.
ZStack also has an alignment parameter, which controls how the child views are aligned within the stack’s bounds (the rectangular area the ZStack occupies on screen). For example, alignment: .topLeading would align all children to the top-left corner.
ZStack(alignment: .bottomTrailing) {
Image(systemName: "figure.fishing.circle")
.resizable()
.aspectRatio(contentMode: .fit)
Text("Photo Credit: SF Symbols")
.padding(4)
.background(Color.black.opacity(0.5))
.foregroundColor(.white)
.font(.caption)
}
In this example, the “Photo Credit” text will be placed at the bottom-right corner of the ZStack, overlaying the background image.

Combining Stacks
Combining stacks allows you to create much more complex and interesting layouts. You can nest HStacks within VStacks, VStacks within HStacks, and any combination with ZStacks.
Example: A Simple Profile View
VStack(alignment: .leading, spacing: 10) {
HStack(spacing: 15) {
Image(systemName: "star.fill")
.resizable()
.frame(width: 60, height: 60)
.foregroundColor(.blue) // Light saber blue!
VStack(alignment: .leading) {
Text("Luke Skywalker")
.font(.title)
Text("Joined: A long time ago")
.font(.subheadline)
.foregroundColor(.gray)
}
}
Text("Bio: Jedi Master, prefers X-Wings over TIE Fighters.")
.font(.body)
}
.padding()

This example uses:
- A main
VStackto arrange elements vertically. - An
HStackinside it for the profile picture (perhaps a Rebel insignia or a generic Star Wars figure) and user details. - Another
VStackinside theHStackfor the name and join date.
Spacers
SwiftUI provides a Spacer view, which is super useful within stacks. A Spacer dynamically expands to fill available space along the stack’s axis.
Example with HStack:
HStack {
Text("Left")
Spacer() // Pushes "Left" and "Right" to opposite ends
Text("Right")
}

Example with VStack:
VStack {
Text("Top")
Spacer() // Pushes "Top" and "Bottom" to opposite ends
Text("Bottom")
}

Spacers are essential for creating flexible layouts that adapt to different screen sizes.
Fixed Spacers
While dynamic Spacers are great for flexible layouts, sometimes you need a fixed amount of space between elements. You can achieve this by providing a minLength to the Spacer initialiser. This tells the spacer to occupy at least that amount of space along the axis of its parent stack.
Example with HStack:
HStack {
Text("Item A")
Spacer(minLength: 50) // Creates a fixed 50-point horizontal space
Text("Item B")
}
Example with VStack:
VStack {
Text("Item X")
Spacer(minLength: 30) // Creates a fixed 30-point vertical space
Text("Item Y")
}
In these examples, Spacer(minLength: 50) in an HStack or Spacer(minLength: 30) in a VStack creates an invisible view that occupies at least that fixed amount of space, pushing other elements apart by that specific dimension. If the available space in the stack is larger than minLength, the spacer will still expand to fill the remaining space, just like a default Spacer. To ensure it only takes up the minLength, you might combine it with .fixedSize() on the Spacer or ensure the stack itself isn’t trying to expand it further, but for simply guaranteeing a minimum separation, minLength is the direct approach.
Conclusion
HStack, VStack, and ZStack are the foundational tools for layout in SwiftUI. By understanding their individual behaviors and how to combine them, you can construct a wide variety of user interfaces. Remember to also leverage Spacers and the alignment and spacing parameters to fine-tune your designs.
As you build more complex views, you’ll find yourself reaching for these stacks constantly. They are simple, yet powerful.
Happy stacking!