The SwiftUI Layout System: Stacks, Frames, Padding, and Spacers
In Ratatouille, every dish at Gusteau’s restaurant is plated with precision — each ingredient is sized, positioned, and spaced deliberately. SwiftUI’s layout system works the same way: it decides how much space each view gets and where it goes on screen, following a clear set of rules.
This guide explains the three-step layout process, then shows you how to control sizing with frame, add breathing room
with padding and Spacer, and align views within stacks. We won’t cover GeometryReader in depth or custom layout
containers — those are covered in their own posts.
This post assumes you are comfortable with SwiftUI basics including Text,
Image, and stacks.
What You’ll Learn
- What Is the Layout System?
- The Three-Step Layout Process
- Controlling Size with frame
- Adding Space with padding
- Pushing Views Apart with Spacer
- Aligning Views in Stacks
- Common Mistakes
- What’s Next?
What Is the Layout System?
The layout system is the set of rules SwiftUI uses to figure out the position and size of every view on screen. Unlike manually setting pixel coordinates, you describe relationships between views (“this text goes above that image, with some padding”) and SwiftUI calculates the exact positions for you.
Think of it like a Pixar storyboard artist working with a director. The director says “put Woody on the left and Buzz on the right, with some space between them.” The artist figures out the exact placement based on the frame size, the characters’ proportions, and the spacing. SwiftUI is the artist — you are the director.
The Three-Step Layout Process
Every layout in SwiftUI follows three steps. Understanding this process is the key to predicting how your views will look.
Step 1: Parent Proposes a Size
The parent view (a stack, a screen, or a frame modifier) tells its child: “Here is how much space you have available.”
Step 2: Child Chooses Its Size
The child view decides how much of that space it actually needs. A Text view, for example, only takes the space needed
to display its string. An Image takes the space of its content. A Color view is greedy — it takes all the space
offered.
Step 3: Parent Places the Child
The parent takes the child’s chosen size and positions it within the available space (usually centered by default).
Here is the process in action:
VStack {
Text("Woody")
Text("Buzz Lightyear")
Color.blue
}
Woody
Buzz Lightyear
┌──────────────────┐
│ │
│ (blue fills │
│ remaining │
│ space) │
│ │
└──────────────────┘
The VStack offers each child an equal share of vertical space. Text("Woody") takes only what it needs for one line.
Text("Buzz Lightyear") does the same. Color.blue is greedy and expands to fill whatever space remains.
Controlling Size with frame
The frame modifier lets you override a view’s natural size by proposing specific dimensions.
Apple Docs: frame(width:height:alignment:) — SwiftUI
Text("Cars")
.frame(width: 200, height: 50)
.background(.orange)
┌────────────────────────┐
│ Cars │
└────────────────────────┘
(orange box, 200 × 50 points)
The frame modifier creates a container that proposes 200×50 points to the Text. The text still centers itself within
that frame by default.
Fixed vs. Flexible Frames
You can set a minimum and maximum range instead of a fixed size. This lets the view adapt to different screen sizes:
Text("The Incredibles")
.frame(minWidth: 100, maxWidth: .infinity)
.background(.red)
┌─────────────────────────────────────┐
│ The Incredibles │
└─────────────────────────────────────┘
(red background stretches to fill available width)
Using maxWidth: .infinity tells the view to take all available horizontal space, like a Color view would. This is a
common pattern for creating full-width buttons or headers.
Adding Space with padding
The padding modifier adds empty space around a view. It is one of the most frequently used modifiers in SwiftUI.
Text("A Bug's Life")
.padding()
.background(.green)
┌───────────────────┐
│ │
│ A Bug's Life │
│ │
└───────────────────┘
(green background with default padding on all sides)
By default, .padding() adds the system-standard amount of space (usually 16 points) on all four sides. You can
customize which edges get padding and how much:
Text("Coco")
.padding(.horizontal, 24)
.padding(.vertical, 8)
.background(.purple)
┌──────────────────┐
│ Coco │
└──────────────────┘
(more padding on left/right, less on top/bottom)
The .horizontal shorthand applies padding to both left and right edges. The .vertical shorthand applies to top and
bottom.
Pushing Views Apart with Spacer
A Spacer is an invisible, flexible view that expands to fill available space. It is like the empty stage area in a
Pixar set — it pushes actors to their marks.
Apple Docs: Spacer — SwiftUI
HStack {
Text("Nemo")
Spacer()
Text("Dory")
}
.padding()
Nemo Dory
The Spacer pushes “Nemo” to the left edge and “Dory” to the right edge, filling all available space between them.
Multiple Spacers
When you use more than one Spacer in a stack, they share the available space equally:
HStack {
Text("Woody")
Spacer()
Text("Buzz")
Spacer()
Text("Jessie")
}
.padding()
Woody Buzz Jessie
The two spacers divide the empty space evenly, creating three equally spaced items. This is a simple way to distribute views across the screen.
Spacer with Minimum Length
You can set a minimum length to ensure spacers never collapse completely:
HStack {
Text("Mike Wazowski")
Spacer(minLength: 20)
Text("Sulley")
}
This guarantees at least 20 points of space between the two names, even on narrow screens.
Aligning Views in Stacks
By default, a VStack centers its children horizontally, and an HStack centers its children vertically. You can
change this with the alignment parameter.
VStack(alignment: .leading) {
Text("Up")
.font(.title)
Text("Directed by Pete Docter")
.font(.subheadline)
Text("2009")
.font(.caption)
.foregroundStyle(.secondary)
}
Up
Directed by Pete Docter
2009
(all text aligned to the left edge)
Without alignment: .leading, all three lines would be centered. The .leading alignment pins every child’s left edge
to the same line.
HStack Alignment
For horizontal stacks, alignment controls the vertical position:
HStack(alignment: .top) {
Text("Short")
.font(.caption)
Text("Tall Text")
.font(.largeTitle)
}
Short
Tall Text
(both views aligned to the top edge)
Common alignment values include .leading, .trailing, .center (default), .top, .bottom, .firstTextBaseline,
and .lastTextBaseline.
Common Mistakes
Using frame When padding Would Suffice
Beginners often use frame to add space around a view when padding is simpler and more flexible.
// ❌ Hardcoded frame for spacing
Text("Soul")
.frame(width: 300, height: 80)
.background(.indigo)
// ✅ Padding adapts to content size
Text("Soul")
.padding(.horizontal, 40)
.padding(.vertical, 20)
.background(.indigo)
The frame approach breaks when the text changes — if the title becomes longer, it might get clipped. Padding adapts to
whatever content is inside it.
Forgetting That Spacer Only Works Inside Stacks
A Spacer outside of an HStack or VStack does nothing useful. It needs a stack to have siblings it can push apart.
// ❌ Spacer has no effect here
VStack {
Spacer()
Text("Luca")
}
// ✅ Spacer pushes Luca to the bottom
VStack {
Spacer()
Text("Luca")
}
.frame(maxHeight: .infinity)
In the corrected version, the VStack is given the full screen height with .frame(maxHeight: .infinity), so the
Spacer has room to expand and push “Luca” to the bottom.
Confusing alignment with Position
The alignment parameter in stacks aligns children relative to each other. It does not position the stack itself on
screen. To position the stack, use frame(maxWidth: .infinity, alignment: .leading) or nest it inside another stack
with a Spacer.
What’s Next?
- SwiftUI uses a three-step layout process: parent proposes, child chooses, parent places.
framesets explicit dimensions; preferpaddingfor adding breathing room.Spaceris a flexible view that pushes siblings apart inside stacks.- Use the
alignmentparameter on stacks to control how children line up. - Greedy views like
Colorexpand to fill space; text and images only take what they need.
With layout under your belt, it is time to make your views respond to user interaction. Head over to
State Management in SwiftUI to learn how @State, @Binding, and
@Observable bring your UI to life.