I originally described SwiftUI’s Tab API as new in iOS 26. That was wrong.
The type-safe Tab syntax, search role, TabSection, and .sidebarAdaptable style arrived with iOS 18. iOS 26 changed how those structures look and behave when an app is built with the newer SDK: the tab bar floats above content, uses Liquid Glass, can minimize while scrolling, and gives a search tab a more distinct transition.
That distinction matters. One release changed how an app describes its navigation; the next changed how the system presents it.
| Release | What changed |
|---|---|
| iOS 18 / iPadOS 18 | Type-safe Tab values, search roles, sections, customization, and adaptable tab/sidebar navigation |
| iOS 26 / iPadOS 26 | Liquid Glass appearance, tab-bar minimization, bottom accessories, and a search tab that can transform into the search field |
I rebuilt the examples in a small Xcode project so I could test the API as a system rather than as isolated snippets.
The Tab Syntax Is About Structure
Before iOS 18, a common SwiftUI tab looked like this:
TabView {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
.tag(Destination.home)
}
The newer form puts the destination, label, image, and selection value in one declaration:
TabView(selection: $selection) {
Tab("Home", systemImage: "house", value: .home) {
HomeView()
}
Tab("Settings", systemImage: "gear", value: .settings) {
SettingsView()
}
}
This is more than shorter syntax. All selection values in the TabView must have the same type, so mismatches become compiler errors instead of navigation bugs. Apple introduced this form in iOS 18, as shown in its WWDC24 session on tabs and sidebars.
For a tab view with no programmatic routing, the value and selection binding are optional:
TabView {
Tab("Home", systemImage: "house") {
HomeView()
}
Tab("Profile", systemImage: "person") {
ProfileView()
}
}
SwiftUI owns the selected-tab interaction and the platform presentation. It does not persist a selected tab across launches unless the app stores that state itself.
A Search Role Is Not a Search Implementation
This was the most important gap in my first example. Tab(role: .search) tells the system which destination represents search, but the role does not filter any data. The app still needs search state, a .searchable modifier, and content that responds to the query.
struct SearchTabView: View {
@State private var query = ""
private let topics = [
"SwiftUI", "UIKit", "Accessibility", "Performance", "Testing"
]
var body: some View {
TabView {
Tab("Articles", systemImage: "newspaper") {
ArticlesView()
}
Tab(role: .search) {
NavigationStack {
List(filteredTopics, id: \.self) { topic in
Text(topic)
}
.navigationTitle("Search")
}
}
}
.searchable(text: $query, prompt: "Search topics")
}
private var filteredTopics: [String] {
query.isEmpty
? topics
: topics.filter { $0.localizedCaseInsensitiveContains(query) }
}
}
The role supplies the default search label, symbol, and pinned placement. In iOS 26, selecting that tab can replace the tab bar with the search field. Apple recommends putting .searchable on the TabView when search applies to the tab-based experience; its iOS 26 SwiftUI design walkthrough demonstrates the complete pattern.
Another correction: multiple search roles are not “undefined behavior.” Apple’s current documentation says a searchable tab view prefers the first tab with the search role. That is still a good reason to declare one clear search destination, but it is different from claiming the app will behave unpredictably.
Badges Are Supplementary State
A numeric badge belongs on the Tab, not inside its destination view:
Tab("Messages", systemImage: "message") {
MessagesView()
}
.badge(unreadCount)
When the integer is zero, the system hides the badge. SwiftUI owns its position, shape, and accessibility presentation. The app owns the count and whether that count is meaningful.
This is a useful boundary: a badge should communicate a small piece of optional information, not become a second label or a permanent decoration.
sidebarAdaptable Does Not Mean “iPad Equals Sidebar”
I had also reduced .sidebarAdaptable to a size-class switch. The real behavior is more interesting.
TabView {
Tab("Home", systemImage: "house") {
HomeView()
}
TabSection("Library") {
Tab("Articles", systemImage: "doc.text") {
ArticlesView()
}
Tab("Videos", systemImage: "video") {
VideosView()
}
}
}
.tabViewStyle(.sidebarAdaptable)
On iPhone, this remains a bottom tab bar. On iPad, it starts as a top tab bar that can expand into a sidebar. On macOS and tvOS, the same style presents as a sidebar; visionOS uses an ornament with a sidebar for secondary tabs. Apple’s tab navigation sample documents those platform differences.
TabSection describes hierarchy; it does not guarantee that every destination will fit into compact tab-bar space. For a large information architecture, Apple shows a deliberate compact-width alternative: expose a smaller set of primary tabs and put the deeper destinations behind a Browse tab. That is more predictable than assuming the system will turn an unlimited hierarchy into a good phone UI.
Customization is opt-in as well. A customizable tab needs a stable .customizationID, a TabViewCustomization binding, and storage such as @AppStorage if the choices should survive a relaunch.
What iOS 26 Adds
When built with the iOS 26 SDK, an ordinary system TabView adopts the new appearance without a custom glass background:
- The tab bar floats above the app’s content.
- Its Liquid Glass material adapts to what scrolls beneath it.
- The app can use
tabBarMinimizeBehavior(_:)to control supported scroll minimization on iPhone. - A tab view can host a bottom accessory for persistent playback or status controls.
- A search-role tab receives the dedicated search transition described above.
I would not recreate that glass surface. The system already coordinates contrast, motion, safe areas, selection, and platform changes. Custom content still needs its own accessibility labels, Dynamic Type behavior, and sensible navigation state; TabView cannot supply those automatically.
The Practical Boundary
After rebuilding the examples, this is the division I use:
| The app should decide | The system should decide |
|---|---|
| Destinations and their hierarchy | Tab-bar or sidebar presentation |
| Selection values and deep-link routing | Material, spacing, and selected appearance |
| Search data and filtering | Search tab’s default label and placement |
| Badge meaning and count | Badge rendering |
| Which destinations deserve compact space | What physically fits at the current size |
The new syntax is valuable because it gives SwiftUI better structural information. The price is that a tab is not a pixel-level drawing command. I am comfortable with that trade when the hierarchy is clear and I test compact and regular layouts early.
Run the Examples
The complete Xcode project is in the SwiftUI tab and toolbar lab. It includes basic tabs, programmatic selection, a working search destination, badges, paging, and sidebar adaptation.
Related posts:
- SwiftUI toolbar placement is intent, not coordinates - See how placement, grouping, and overflow adapt to available space
- SwiftUI Liquid Glass and Accessibility - Test system chrome with Reduce Motion and VoiceOver
One quick signal
Did this earn your time?
Thanks. That gives me something concrete to check.


