Sitemap
Level Up Coding

Coding tutorials and news. The developer homepage gitconnected.com && skilled.dev && levelup.dev

SwiftUI: Toolbar Customization

7 min readJun 30, 2025

--

First of all, when I say toolbar, I probably meant a combination of the toolbar, the navigation bar and the bottom bar!

*************

Now! Have to be honest here!

There is nothing special nor complicated about tool bar customization ( except for couple little teeny tiny points to pay attention to)!

Then why did I still decide to write this article?

I will not let you know this but until this year’s WWDC where Apple has introduced couple new toolbar features, I totally did not realize that we can customize those toolbars this much!

(I know! Ignorance! Not knowing that I don’t know! Worst thing for human being!)

So!

This article is just so that we are aware of the existence of those amazing structs and modifiers we have to customize toolbars !

Here is what we have.

  1. Set Subtitle
  2. Customize Toolbar items (Grouping, combination with system items, spacer, shared background)
  3. Add Title Menu
  4. Set role
  5. Control toolbar visibility
  6. Customize background (visibility and style)
  7. Control color scheme

Plus couple little points to attention to and couple other modifiers that are not available on iOS.

Also, please keep in mind that most of the styling we are setting here are just our preference. The system (SwiftUI) gets to decide whether they are enforced or not, and this can depend on the OS we are running, the number of toolbar items we have, and etc!

Subtitle

I don’t know what takes Apple so long, but we can finally give our Toolbar navigation title a subtitle without using all those work arounds! By simply applying this navigationSubtitle(_:) modifier!

NavigationStack {
VStack {

}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.yellow.opacity(0.1))
.navigationTitle("Title")
.navigationSubtitle("Subtitle")
.navigationBarTitleDisplayMode(.inline)
}
Display mode: large
Display mode: inline

Toolbar Items

Item Group

Instead of having a bunch of independent ToolbarItems, we get this new ToolbarItemGroup that represents a group of ToolbarItems which can be placed in the toolbar or navigation bar.

.toolbar {
ToolbarItemGroup(placement: .topBarTrailing, content: {
HStack {
Image(systemName: "house.fill")
Image(systemName: "heart.fill")
Image(systemName: "leaf.fill")
}
})
}

Combination With System Item

We can combine our custom ToolbarItems with system ones such as the search interface or the title with the ToolbarDefaultItem.

For example, let’s use it to place the search interface between some other ToolbarItems by setting the ToolbarDefaultItemKind to search.

.searchable(text: .constant(""))
.toolbar {
ToolbarItem(placement: .bottomBar) {
Image(systemName: "heart.fill")
}
DefaultToolbarItem(kind: .search, placement: .bottomBar)
ToolbarItem(placement: .bottomBar) {
Image(systemName: "star.fill")
}
}

Other ToolbarDefaultItemKind available are

  • sidebarToggle: The sidebar toggle toolbar item a NavigationSplitView adds by default.
  • title: The title and subtitle shown in title bar / navigation bar.

Item Spacing

ToolbarSpacer is a space item creates visual breaks in the toolbar between items. It can either have a standard fixed size or be flexible and push items apart.

.toolbar {
ToolbarItem(placement: .bottomBar) {
Image(systemName: "heart.fill")
}
DefaultToolbarItem(kind: .search, placement: .bottomBar)
ToolbarSpacer(.fixed, placement: .bottomBar)
ToolbarItem(placement: .bottomBar) {
Image(systemName: "star.fill")
}
}

Or if set to flexible

Disable Shared Background

As we can see from the image above, the items that are not spaced by a ToolbarSpacer (items in the same logical grouping) automatically shared that glass background effect.

Get Itsuki’s stories in your inbox

Join Medium for free to get updates from this writer.

We can disable this shared background visibility by using the sharedBackgroundVisibility(_:) modifier.

.toolbar {
ToolbarItem(placement: .bottomBar) {
Image(systemName: "heart.fill")
}
.sharedBackgroundVisibility(.hidden)

DefaultToolbarItem(kind: .search, placement: .bottomBar)

// ...
}

Toolbar Title Menu

A toolbar title menu is a menu that can be presented by tapping the navigation title in the app’s navigation bar.

There are two ways of adding it!

One is by using the toolbar(content:) modifier just like we would for other toolbar item contents and pass in a ToolbarTitleMenu structure.

.navigationTitle("Toolbar Customization")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarTitleMenu {
Button(action: {}, label: {
Text("Some Action")
})
}
}

Another by using the toolbarTitleMenu(content:) modifier directly.

.navigationTitle("Toolbar Customization")
.navigationBarTitleDisplayMode(.inline)
.toolbarTitleMenu {
Button(action: {}, label: {
Text("Some Action")
})
}

Little Note

The toolbar display mode has to be inline for the menu popup (or that down chevron) to show up!

We can set the display mode by either using the navigationBarTitleDisplayMode modifier or the toolbarTitleDisplayMode modifier.

Toolbar Role

toolbarRole(_:) configure the semantic ToolbarRole for content populating the toolbar. Setting this value will also affect where the toolbar places the title contents. For example, a browser will automatically leading align the title.

.navigationTitle("navigationStack")
.navigationBarTitleDisplayMode(.inline)
.toolbarRole(.navigationStack)

Similar to the toolbar title menu above, this change in placement will only take effect if the toolbar display mode is inline. And of course, it is not guaranteed and where the system end up placing it may depends other factors such as the number of toolbar items you have.

Toolbar Visibility

We can hide the toolbar by using the toolbarVisibility(_:for:) modifier. For bars, we can provide multiple ToolbarPlacement instances to hide multiple bars at once.

.toolbarVisibility(.visible, for:.navigationBar, .tabBar)
.toolbarVisibility(.hidden, for: .bottomBar)

Background

Visibility

Similar to the toolbar visibility above, we also get this toolbarBackgroundVisibility(_:for:) to specify the preferred visibility of backgrounds on a bar.

.toolbarBackgroundVisibility(.visible, for: .navigationBar, .tabBar, .bottomBar)

For example, if we have some Image or Text below(z-axis-wise) the toolbar, we can set the Visibility to hidden to create a nice translucent effects when we want to show those views.

Style

If we want to set the preferred shape style of the background, we can use the toolbarBackground(_:for:) modifier.

.toolbarBackgroundVisibility(.visible, for: .navigationBar, .tabBar, .bottomBar)
.toolbarBackground(.yellow, for: .navigationBar)

Note on Background Color

  1. Background color will not show up if the background itself is not visible! That’s why we have explicitly setting background to visible using toolbarBackgroundVisibility here!
  2. Styles such as gradient will not work (as expected). For example, if we have a LinearGradient(colors: [.red, .yellow, .blue], startPoint: .top, endPoint: .bottom), the color will just be red!

Foreground Style

For watchOS, we can also set the preferred foreground style just like we would for the background above using the toolbarForegroundStyle(_:for:) modifier.

.toolbarForegroundStyle(.blue, for: .navigationBar)

Color scheme

If we want to set our toolbar color scheme independently from the rest of our view, we have the toolbarColorScheme(_:for:) modifier.

.toolbarBackgroundVisibility(.visible, for: .navigationBar, .tabBar, .bottomBar)
.toolbarBackground(.yellow, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar)

(I know, the title and subtitle are almost not readable…)

And similar to the toolbarBackground, the provided color scheme is only respected while a background is visible!

Label Style

There is a windowToolbarLabelStyle(_:) modifier or windowToolbarLabelStyle(fixed:) to customize the label style of items in a toolbar, with the former one enables user customization.

struct Demo: App {
var body: some Scene {
WindowGroup {
ContentView()
.toolbar {}
}
.windowToolbarLabelStyle(fixed: .iconOnly)
}
}

The modifier itself is available on all platforms, but for iOS, the only value for ToolbarLabelStyle we have access to is automatic.

For other platforms such as MacOS, we also have access to iconOnly, titleAndIcon, and titleOnly.

Code Snippet

A little code for you to try it out by yourself!


struct ToolbarStyling: View {

var body: some View {
NavigationStack {
VStack {
Text("Toolbar Customization")
.font(.title)
.fontWeight(.bold)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.yellow.opacity(0.1))
.navigationTitle("Title")
.navigationSubtitle("Subtitle")
.navigationBarTitleDisplayMode(.inline)
.toolbarRole(.automatic)
.toolbar {
ToolbarItemGroup(placement: .topBarTrailing, content: {
HStack {
Image(systemName: "house.fill")
Image(systemName: "heart.fill")
Image(systemName: "leaf.fill")
}
})
ToolbarSpacer(.fixed, placement: .topBarTrailing)
ToolbarItem(placement: .topBarTrailing, content: {
Image(systemName: "star.fill")
})
}
.searchable(text: .constant(""))
.toolbar {
ToolbarItem(placement: .bottomBar) {
Image(systemName: "heart.fill")
}
.sharedBackgroundVisibility(.hidden)

ToolbarSpacer(.fixed, placement: .bottomBar)

DefaultToolbarItem(kind: .search, placement: .bottomBar)

ToolbarSpacer(.fixed, placement: .bottomBar)

ToolbarItem(placement: .bottomBar) {
Image(systemName: "star.fill")
}
}
.searchPresentationToolbarBehavior(.avoidHidingContent)
.toolbarTitleMenu {
Button(action: {}, label: {
Text("Main Action")
})
Menu(content: {
Section("Actions") {
Button(action: {}, label: {
Text("Menu Action")
})
}

Picker(selection: .constant(1), content: {
Text("Picker Option 1").tag(1)
Text("Picker Option 2").tag(2)
}, label: {
Text("Picker Options")
})
.pickerStyle(.menu)
}, label: {
Text("Menu")
})
}
.toolbarVisibility(.visible, for: .navigationBar, .tabBar, .bottomBar)
.toolbarBackgroundVisibility(.visible, for: .navigationBar, .tabBar, .bottomBar)
.toolbarBackground(.yellow, for: .navigationBar)
.toolbarColorScheme(.light, for: .navigationBar)

}
}
}

Thank you for reading!

That’s it for this article!

Happy toolbar-ing!

--

--