Fix: SwiftUI Form Section Buttons Not Horizontally Centered

Fix: SwiftUI Form Section Buttons Not Horizontally Centered

Written by

in

Quick answer: Form rows are List rows: the row spans the full width while its content keeps its intrinsic size and sits leading-aligned. Apply .frame(maxWidth: .infinity, alignment: .center) to the content inside the Section (or wrap it in HStack { Spacer(); content; Spacer() }) to center it.

Why this happens

A Form is a styled List under the hood, and every child of a Section becomes a row. The row itself always takes the full available width, but the content you put inside it keeps its intrinsic size and is laid out leading-aligned by default. That’s why your VStack of buttons hugs the left edge no matter what you do to the VStack itself.

Setting alignment: .center on the VStack or on the Section doesn’t help because the alignment applies to the content inside the already-full-width row container — the row’s own layout still places that content at the leading edge. Form and Section intentionally use list-style row layout, so centering the container alone is never enough; you have to make the content expand to the row width and center within it.

This also explains the behavior you noticed in the top section: Text with a long string fills the row width, so the shorter Text("Title") above it appears to center automatically — it’s actually being centered within the VStack by the wider sibling text, not by the row.

How to fix it

1. Use frame(maxWidth: .infinity, alignment: .center) — the cleanest fix

Apply the frame modifier to the content inside the row. This makes the content claim the full row width and centers whatever is inside it:

import SwiftUI

struct Testing: View {
    var body: some View {
        Form {
            Section {
                VStack {
                    Text("Title")
                        .font(.largeTitle)
                    Text("And more text here.")
                }
            }

            Section {
                VStack {
                    Text("TODO: Center!")
                    Button("Abc", action: abc).buttonStyle(.borderedProminent)
                    Button("Def", action: def).buttonStyle(.borderedProminent)
                    Button("Xyz", action: xyz).buttonStyle(.borderedProminent)
                }
                .frame(maxWidth: .infinity, alignment: .center)
            }
        }
    }

    func abc() {}
    func def() {}
    func xyz() {}
}

If you want each button itself centered rather than the group as a block, apply the frame to each control instead:

Section {
    VStack {
        Text("TODO: Center!")
            .frame(maxWidth: .infinity, alignment: .center)
        Button("Abc", action: abc)
            .buttonStyle(.borderedProminent)
            .frame(maxWidth: .infinity, alignment: .center)
        Button("Def", action: def)
            .buttonStyle(.borderedProminent)
            .frame(maxWidth: .infinity, alignment: .center)
        Button("Xyz", action: xyz)
            .buttonStyle(.borderedProminent)
            .frame(maxWidth: .infinity, alignment: .center)
    }
}

2. Wrap the content in an HStack with Spacers

If you prefer not to use frame, or you’re centering a single small control, the Spacer approach works because Spacer expands to fill the remaining row width:

Section {
    HStack {
        Spacer()
        VStack {
            Text("TODO: Center!")
            Button("Abc", action: abc).buttonStyle(.borderedProminent)
            Button("Def", action: def).buttonStyle(.borderedProminent)
            Button("Xyz", action: xyz).buttonStyle(.borderedProminent)
        }
        Spacer()
    }
}

This centers the whole VStack as a group. Like fix 1, if you want each button individually centered, put the HStack { Spacer(); ...; Spacer() } around each one.

3. Centering a single control without a wrapper

For one button or label, you can skip the extra container entirely:

Section {
    Button("Abc", action: abc)
        .buttonStyle(.borderedProminent)
        .frame(maxWidth: .infinity, alignment: .center)
}

or, equivalently:

Section {
    HStack {
        Spacer()
        Button("Abc", action: abc)
            .buttonStyle(.borderedProminent)
        Spacer()
    }
}

Both produce the same result; pick whichever reads better in your codebase and stay consistent.

How to prevent it

  • Remember the rule: anything inside a Form/Section is a List row — full width, leading-aligned content. Center with .frame(maxWidth: .infinity, alignment: .center) on the content, never on the Section.
  • When centering a group of controls, wrap them in a VStack first, then center the VStack — otherwise each control centers independently and the group looks ragged.
  • Don’t rely on long Text strings to push other content toward the center; that only works by accident and breaks when the text wraps or the string changes.
  • If a centered control still looks off, check whether a buttonStyle or row default is giving it an unexpected width — borderedProminent buttons size to their label, which is what makes the frame/Spacer approach necessary.

FAQ

Why doesn’t alignment: .center on my VStack work inside a Form?

Because the VStack’s content already fits its intrinsic size — there’s nothing inside the VStack to center. The misalignment comes from the row placing the VStack at the leading edge, so the frame or Spacer has to be applied at the row level.

Does this apply to List as well as Form?

Yes. Form is a styled List, so rows in a plain List behave the same way: full-width rows with leading-aligned intrinsic-size content. The same .frame(maxWidth: .infinity, alignment: .center) fix works there.

How do I center each button individually instead of the group?

Apply .frame(maxWidth: .infinity, alignment: .center) to each button (or wrap each in HStack { Spacer(); button; Spacer() }) rather than wrapping the whole VStack. The buttons will each sit on the horizontal center line of the row.

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.