avatar
Published on

Add Spacing to Toolbars with ToolbarSpacer in SwiftUI

Authors
  • avatar
    Name
    Mick MacCallum
    Twitter
    @0x7fs

iOS 26 introduced ToolbarSpacer, a dedicated API for adding spacing between toolbar items. Before iOS 26, controlling spacing required workarounds using ToolbarItemGroup and regular Spacer() views. Now you have a clean, purpose-built solution with both fixed and flexible spacing options.

Basic Usage

Place ToolbarSpacer between toolbar items to add space:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            Text("Content")
                .toolbar {
                    ToolbarItem {
                        Button("Delete", systemImage: "trash", role: .destructive) { }
                    }

                    ToolbarSpacer(.fixed)

                    ToolbarItem {
                        Button("Add", systemImage: "plus") { }
                    }
                }
        }
    }
}

This creates a fixed amount of space between the Delete and Add buttons.

Fixed vs Flexible Spacing

ToolbarSpacer supports two spacing modes:

Fixed Spacing

Fixed spacing adds a consistent, system-defined gap between items:

.toolbar {
    ToolbarItem { Button("First", systemImage: "1.circle") { } }
    ToolbarSpacer(.fixed)
    ToolbarItem { Button("Second", systemImage: "2.circle") { } }
    ToolbarSpacer(.fixed)
    ToolbarItem { Button("Third", systemImage: "3.circle") { } }
}

Use fixed spacing for:

  • Consistent separation between related items
  • Visual grouping
  • Predictable layout

Flexible Spacing

Flexible spacing expands to fill available space, pushing items apart:

.toolbar {
    ToolbarItem { Button("Left", systemImage: "arrow.left") { } }
    ToolbarSpacer(.flexible)
    ToolbarItem { Button("Right", systemImage: "arrow.right") { } }
}

Use flexible spacing for:

  • Items at opposite ends of the toolbar
  • Maximum separation between groups
  • Dynamic spacing that adapts to available space

Combining Multiple Spacers

Create sophisticated layouts by combining spacers:

.toolbar {
    ToolbarItem { Button("Action 1", systemImage: "1.circle") { } }
    ToolbarItem { Button("Action 2", systemImage: "2.circle") { } }

    ToolbarSpacer(.flexible)

    ToolbarItem { Button("Action 3", systemImage: "3.circle") { } }
    ToolbarSpacer(.fixed)
    ToolbarItem { Button("Action 4", systemImage: "4.circle") { } }
}

This groups Action 1 and 2 on the left, then uses flexible spacing to push Action 3 and 4 to the right with fixed spacing between them.

Targeting Specific Placements

Specify placement for spacers in specific toolbar regions:

.toolbar {
    ToolbarItem(placement: .topBarLeading) {
        Button("Leading", systemImage: "arrow.left") { }
    }

    ToolbarSpacer(.fixed, placement: .topBarLeading)

    ToolbarItem(placement: .topBarLeading) {
        Button("Also Leading", systemImage: "star") { }
    }

    ToolbarItem(placement: .topBarTrailing) {
        Button("Trailing", systemImage: "arrow.right") { }
    }
}

Practical Example: Document Editor

Here's a toolbar with multiple action groups:

struct DocumentEditor: View {
    var body: some View {
        NavigationStack {
            TextEditor(text: .constant("Document content"))
                .navigationTitle("Document")
                .toolbar {
                    ToolbarItem(placement: .cancellationAction) {
                        Button("Cancel", systemImage: "xmark") { }
                    }

                    ToolbarItemGroup(placement: .primaryAction) {
                        Button("Bold", systemImage: "bold") { }
                        Button("Italic", systemImage: "italic") { }
                        Button("Underline", systemImage: "underline") { }

                        ToolbarSpacer(.fixed)

                        Button("Link", systemImage: "link") { }
                    }
                }
        }
    }
}

Bottom Toolbar Example

ToolbarSpacer works in bottom toolbars too:

struct PhotoEditor: View {
    var body: some View {
        NavigationStack {
            Image(systemName: "photo")
                .resizable()
                .scaledToFit()
                .toolbar {
                    ToolbarItemGroup(placement: .bottomBar) {
                        Button("Rotate", systemImage: "rotate.right") { }
                        Button("Crop", systemImage: "crop") { }

                        ToolbarSpacer(.flexible)

                        Button("Filters", systemImage: "camera.filters") { }

                        ToolbarSpacer(.flexible)

                        Button("Adjust", systemImage: "slider.horizontal.3") { }
                    }
                }
        }
    }
}

Comparing with Pre-iOS 26 Approaches

Before iOS 26, you had to use workarounds:

// Before iOS 26
.toolbar {
    ToolbarItemGroup(placement: .topBarTrailing) {
        Button("First") { }
        Spacer()
        Button("Second") { }
    }
}

// iOS 26+
.toolbar {
    ToolbarItem { Button("First") { } }
    ToolbarSpacer(.flexible)
    ToolbarItem { Button("Second") { } }
}

The new approach is cleaner and more explicit.

Best Practices

Use Fixed for Grouping

Visually group related items with fixed spacing:

.toolbar {
    // Text formatting group
    ToolbarItem { Button("Bold") { } }
    ToolbarSpacer(.fixed)
    ToolbarItem { Button("Italic") { } }
    ToolbarSpacer(.fixed)
    ToolbarItem { Button("Underline") { } }

    ToolbarSpacer(.flexible)

    // Alignment group
    ToolbarItem { Button("Left") { } }
    ToolbarSpacer(.fixed)
    ToolbarItem { Button("Center") { } }
}

Use Flexible for Balance

Create balanced, symmetrical layouts:

.toolbar {
    ToolbarItem { Button("Left") { } }
    ToolbarSpacer(.flexible)
    ToolbarItem { Button("Center") { } }
    ToolbarSpacer(.flexible)
    ToolbarItem { Button("Right") { } }
}

Consider Touch Targets

Ensure adequate spacing for comfortable tapping:

// Good: Fixed spacing ensures minimum separation
.toolbar {
    ToolbarItem { Button("A") { } }
    ToolbarSpacer(.fixed)
    ToolbarItem { Button("B") { } }
}

Maintain Visual Hierarchy

Use spacing to communicate importance:

.toolbar {
    ToolbarItem { Button("Save") { } }
    ToolbarSpacer(.fixed)
    ToolbarItem { Button("Share") { } }

    ToolbarSpacer(.flexible)

    ToolbarItem { Button("Delete", role: .destructive) { } }
}

Working with ToolbarItemGroup

ToolbarSpacer works seamlessly with ToolbarItemGroup:

.toolbar {
    ToolbarItemGroup(placement: .topBarTrailing) {
        Button("Edit", systemImage: "pencil") { }
        Button("Copy", systemImage: "doc.on.doc") { }

        ToolbarSpacer(.fixed)

        Button("Share", systemImage: "square.and.arrow.up") { }

        ToolbarSpacer(.flexible)

        Button("More", systemImage: "ellipsis") { }
    }
}