How to Make a VStack Fill the Screen Width in SwiftUI

How to Make a VStack Fill the Screen Width in SwiftUI

Written by

in

Quick answer: To make a VStack fill the entire available width, attach .frame(maxWidth: .infinity) directly to the stack. You can also specify an alignment, such as .frame(maxWidth: .infinity, alignment: .leading), to keep its internal contents pinned to one edge.

Why this happens

In SwiftUI, views negotiate their final sizes through a three-step layout process:

  1. The parent container proposes a size to the child view.
  2. The child view determines its own size based on its internal content and layout rules.
  3. The parent container positions the child within its coordinate space.

A VStack calculates its horizontal footprint by measuring its widest subview. Unlike layout containers that greedily consume all offered space, a VStack wraps snugly around its children along the horizontal axis. If the stack contains only short text labels or narrow shapes, its width stays constrained to those elements, even if you set a vertical Spacer() inside to stretch it top-to-bottom.

Applying a background modifier like .background(Color.red) directly to a default VStack demonstrates this behavior immediately: the background color wraps tightly around the text rather than expanding edge-to-edge across the display.

How to fix it

1. Apply a flexible frame modifier (maxWidth: .infinity)

The standard, most robust solution in all versions of SwiftUI is to apply a flexible .frame() modifier directly to the VStack. Setting maxWidth: .infinity instructs the frame to accept all horizontal space proposed by its parent.

import SwiftUI

struct FullWidthVStackView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Title")
                .font(.title)
                .bold()

            Text("This text now sits in a container that spans the entire screen width.")
                .font(.body)

            Spacer()
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(Color.red.opacity(0.2))
    }
}

By passing alignment: .leading to .frame(), the content inside the stack stays aligned to the leading edge even though the frame itself expands across the full display width. Note that order matters: modifiers placed before .frame(maxWidth: .infinity) apply to the snug content, while modifiers placed after it apply to the expanded container.

2. Use containerRelativeFrame (iOS 17 and later)

If your deployment target is iOS 17.0, macOS 14.0, or newer, you can use the containerRelativeFrame modifier. This modifier queries the nearest container (such as a window, screen, or scroll view) and sizes the view proportionally along the requested axes.

import SwiftUI

struct ContainerRelativeWidthView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("Header")
                .font(.headline)

            Text("Subheadline content goes here.")
                .font(.subheadline)

            Spacer()
        }
        .containerRelativeFrame(.horizontal, alignment: .leading)
        .background(Color.blue.opacity(0.15))
    }
}

This method is particularly effective inside custom scrollable layouts or split-view presentations where you want precise control relative to the parent container’s geometry without calculating manual offsets.

3. Expand an individual child view

Because a VStack expands horizontally to fit its widest subview, making any single child view occupy the full width automatically forces the entire stack to expand with it.

import SwiftUI

struct ChildExpandedVStackView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Full Width Header")
                .font(.title2)
                .frame(maxWidth: .infinity, alignment: .leading)

            Text("Short line.")
                .font(.body)

            Spacer()
        }
        .background(Color.green.opacity(0.2))
    }
}

Alternatively, an embedded HStack containing a horizontal Spacer() can push the bounds out, although applying .frame(maxWidth: .infinity) directly on the VStack or its primary element produces cleaner, more predictable hierarchy trees in Xcode’s View Debugger.

How to prevent it

  • Check modifier order: Always place sizing modifiers like .frame(maxWidth: .infinity) before background colors, borders, and gestures so they cover the entire stretched area.
  • Distinguish stack alignment from frame alignment: Setting VStack(alignment: .leading) aligns child views relative to one another; it does not force the stack itself to touch the left side of the screen. Pair it with .frame(maxWidth: .infinity, alignment: .leading) for full-width leading alignment.
  • Avoid nested dummy spacers: Do not nest empty HStack { Spacer() } rows simply to force horizontal width. They add unnecessary nodes to the layout tree and can distort keyboard avoidance or accessibility frames.

FAQ

Why doesn’t Spacer() inside a VStack expand its width?

A Spacer placed directly inside a VStack only expands along the stack’s primary axis, which is vertical. To expand horizontally using spacers, the Spacer must be placed inside an HStack or given a flexible frame.

Does maxWidth: .infinity ignore safe area insets?

No. A flexible frame expands only to the boundary of the safe area proposed by its parent view. If you want the background to touch the physical edges of the display, add .ignoresSafeArea() to the background modifier or stack.

How do I center the content while keeping the VStack full-width?

Set the frame without a specific horizontal alignment: .frame(maxWidth: .infinity). The frame will stretch across the screen, and SwiftUI defaults to centering the stack’s content within that expanded space.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.