Stacks: VStack, HStack, and ZStack
VStack, HStack, and ZStack are SwiftUI's foundational layout containers. VStack arranges its children top to bottom, HStack arranges them left to right (respecting right-to-left locales automatically), and ZStack layers children back to front along the z-axis, overlapping them at the same position. Nearly every SwiftUI screen is built by nesting these three containers, combined with spacing, alignment, and modifiers, to arrange more complex layouts than any single stack could express alone.
Cricket analogy: VStack arranges batting order top to bottom like a scorecard, HStack lines up fielders left to right along the boundary, and ZStack overlays the run-rate graphic on top of the live scoreboard at the same position.
Spacing and Alignment
Both VStack and HStack accept an alignment parameter (.leading, .center, .trailing for VStack; .top, .center, .bottom for HStack) and a spacing parameter controlling the gap between children. ZStack accepts a two-dimensional alignment (like .topLeading or .bottomTrailing) since it has no single natural axis. When no spacing value is given, SwiftUI uses a platform-appropriate default gap rather than zero, which surprises many newcomers who expect elements to sit flush against each other.
Cricket analogy: A VStack of the day's fall-of-wickets uses .leading alignment to left-align batsman names, while an HStack of the scoreboard's run tally uses .center with default spacing that surprises newcomers by not sitting flush.
struct BadgeCard: View {
var body: some View {
ZStack(alignment: .topTrailing) {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: "star.fill")
.foregroundStyle(.yellow)
Text("Featured Course")
.font(.headline)
}
Text("SwiftUI Fundamentals")
.font(.title3.bold())
Text("12 lessons \u{2022} 4h total")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
Text("NEW")
.font(.caption2.bold())
.padding(6)
.background(.red, in: Capsule())
.foregroundStyle(.white)
.padding(8)
}
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16))
}
}Nesting Stacks for Complex Layouts
Real layouts almost always nest stacks: an HStack of icon-and-label pairs inside a VStack of list rows, or a ZStack overlaying a badge on top of a VStack-based card, as in the example above. Because each stack is itself just a view conforming to View, there's no limit (besides readability and compiler type-checking time for very deeply nested @ViewBuilder expressions) to how deeply they can be composed.
Cricket analogy: A scorecard app nests an HStack of a batsman's name-and-runs pair inside a VStack of the full batting order, and a ZStack overlays a 'not out' badge on top of a VStack-based player card, just as real layouts nest stacks.
Stacks size themselves to fit their content by default (a 'hug' behavior), unlike UIKit's UIStackView, which can be configured with distribution and fill rules. To make a stack fill available space, apply .frame(maxWidth: .infinity) explicitly.
In a ZStack, later children are drawn on top of earlier ones. Forgetting this ordering is a common source of bugs when a badge or overlay unexpectedly appears behind its background.
VStacklays out children vertically,HStackhorizontally, andZStacklayers them by depth.alignmentandspacingparameters control how children are positioned withinVStack/HStack.ZStackuses a two-dimensional alignment like.topTrailingsince it has no single primary axis.- Stacks hug their content's size by default; use
.frame(maxWidth: .infinity)to expand. - In
ZStack, later-declared children render on top of earlier ones. - Complex layouts are built by nesting stacks inside one another, not through a single all-purpose container.
Practice what you learned
1. Which stack arranges its children left to right?
2. How does `ZStack` layer overlapping children?
3. What alignment values does `ZStack` accept, and why?
4. By default, how do VStack and HStack size themselves relative to their children?
5. How would you make a VStack expand to fill all available horizontal space?
Was this page helpful?
You May Also Like
Views and the View Protocol
How the `View` protocol works, what `body` must return, and how SwiftUI composes small views into larger interfaces.
Modifiers in SwiftUI
How view modifiers work under the hood, why order matters, and how chained modifiers build up new wrapped view types.
List and ForEach
Learn how List renders scrollable, platform-styled collections and how ForEach generates repeated views from a data collection using stable identity.
LazyVStack and LazyHStack
Understand how LazyVStack and LazyHStack defer creation of off-screen views inside a ScrollView, enabling efficient custom scrolling layouts.