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 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.
  • frame sets explicit dimensions; prefer padding for adding breathing room.
  • Spacer is a flexible view that pushes siblings apart inside stacks.
  • Use the alignment parameter on stacks to control how children line up.
  • Greedy views like Color expand 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.