SwiftUI Tutorial · iOS 16+ · iOS 26 Liquid Glass

SwiftUI TabView: Tabs, Paging & the iOS 26 Tab API

Build a bottom tab bar, a swipeable paged carousel, and a badge-driven navigation shell — plus the new iOS 26 Tab and TabSection API with a floating Liquid Glass tab bar.

Last updated: 2026-07-17 10 min read By Ahmed Gagan, iOS Engineer
Quick Answer

SwiftUI's TabView creates a tab-based interface: place child views inside and mark each with .tabItem { Label(...) }. Bind a selection to switch tabs programmatically with TabView(selection:). Switch to a swipeable carousel with .tabViewStyle(.page), and add unread counts with .badge(_:). On iOS 26 the new Tab and TabSection value types replace .tabItem and power a floating Liquid Glass tab bar.

Classic API
TabView { view.tabItem { Label(...) } }
Paging style
.tabViewStyle(.page) for swipeable carousels
iOS 26 API
Tab(value:) and TabSection with Liquid Glass tab bar
Swift Kit support
The Swift Kit ships a ready-made TabView shell with badge-aware tabs and deep-link routing.

The classic TabView

In its simplest form, a TabView takes a set of child views and a .tabItem on each. SwiftUI renders a bottom tab bar on iPhone and adapts the layout on iPad and Mac. Each tab keeps its own view state as you switch, so scroll position and form input persist. Keep tab items to a Label with a title and SF Symbol; if you exceed five tabs, iOS collapses the overflow into a More tab automatically.

A three-tab interface
TabView {
    HomeView()
        .tabItem { Label("Home", systemImage: "house") }

    SearchView()
        .tabItem { Label("Search", systemImage: "magnifyingglass") }

    ProfileView()
        .tabItem { Label("Profile", systemImage: "person.crop.circle") }
}

Programmatic selection binding

To control which tab is active from code — for example, to jump to a tab after a push notification — bind a selection value. Give each tab a .tag matching the type of the selection state, then mutate that state to switch tabs. Using an enum for the tag makes the code self-documenting and prevents typos that a raw Int would allow. This binding is also how you implement scroll-to-top on a second tap of the active tab.

Enum-tagged tabs with a binding
enum AppTab: Hashable { case home, search, profile }

struct RootView: View {
    @State private var selection: AppTab = .home

    var body: some View {
        TabView(selection: $selection) {
            HomeView()
                .tabItem { Label("Home", systemImage: "house") }
                .tag(AppTab.home)

            SearchView()
                .tabItem { Label("Search", systemImage: "magnifyingglass") }
                .tag(AppTab.search)
        }
    }
}

Paged carousels with .tabViewStyle(.page)

The same TabView container becomes a swipeable, full-screen carousel when you apply .tabViewStyle(.page). This is the idiomatic way to build onboarding walkthroughs and image galleries without a third-party library. Add .indexViewStyle to control the page dots, and bind a selection if you want a Next button to advance pages programmatically. Wrap page content in a GeometryReader only if you need precise sizing; usually the container fills the screen for you.

A swipeable onboarding carousel
struct Onboarding: View {
    @State private var page = 0
    private let slides = ["Welcome", "Sync", "Go Pro"]

    var body: some View {
        TabView(selection: $page) {
            ForEach(slides.indices, id: \.self) { i in
                Text(slides[i])
                    .font(.largeTitle)
                    .tag(i)
            }
        }
        .tabViewStyle(.page)
        .indexViewStyle(.page(backgroundDisplayMode: .always))
    }
}

Badges for unread counts

Attach a badge to any tab with the .badge modifier to surface unread counts or a status dot. Pass an Int for a numeric badge, a String for text, or zero to hide it. Badges are perfect for a messages tab or a notifications center. Because the modifier lives on the tab content, you can drive the value from observable state and it updates live as new items arrive.

A numeric badge on a tab
InboxView()
    .tabItem { Label("Inbox", systemImage: "envelope") }
    .badge(unreadCount)   // Int; 0 hides the badge

SettingsView()
    .tabItem { Label("Settings", systemImage: "gear") }
    .badge("New")          // String badge

The iOS 26 Tab API and Liquid Glass tab bar

iOS 26 introduces a value-type Tab API that replaces the .tabItem modifier. You declare Tab(title, systemImage:, value:) items directly, group related tabs with TabSection, and mark a search tab with role: .search so it docks correctly in the new floating Liquid Glass tab bar. The bar floats above content and adopts the glass material automatically. Keep an availability fork so devices on iOS 16–25 continue to use the classic .tabItem syntax.

iOS 26 Tab and TabSection
@available(iOS 26, *)
struct ModernRoot: View {
    @State private var selection: AppTab = .home

    var body: some View {
        TabView(selection: $selection) {
            Tab("Home", systemImage: "house", value: .home) {
                HomeView()
            }
            Tab("Search", systemImage: "magnifyingglass", value: .search, role: .search) {
                SearchView()
            }
            TabSection("Library") {
                Tab("Saved", systemImage: "bookmark", value: .profile) {
                    SavedView()
                }
            }
        }
    }
}

A tab shell that just works

The Swift Kit gives you a TabView root with enum-tagged tabs, live badges, and deep-link routing already wired, so you start above the boilerplate.

Get The Swift Kit — $99

Frequently Asked Questions

How do I make a swipeable paged carousel with TabView in SwiftUI?
Apply .tabViewStyle(.page) to a TabView and put your slides inside a ForEach with a .tag on each. Add .indexViewStyle(.page(backgroundDisplayMode: .always)) to show the page dots. Bind a selection if you want to advance pages from a button.
How do I switch the selected tab programmatically in SwiftUI TabView?
Use TabView(selection:) bound to @State, give each tab a matching .tag, and set the state value to switch tabs. An enum tag is safer than an Int because the compiler catches mismatches.
How do I add a badge to a tab in SwiftUI TabView?
Attach .badge(_:) to the tab content. Pass an Int for a count, a String for text, or 0 to hide it. Drive the value from observable state so it updates live as new items arrive.
What is the iOS 26 Tab API in SwiftUI TabView?
iOS 26 replaces .tabItem with a value-type Tab(title, systemImage:, value:) declared directly in the TabView, plus TabSection to group tabs. A tab with role: .search docks in the new floating Liquid Glass tab bar.
Why does my SwiftUI TabView show a More tab?
iPhone collapses tabs beyond the fifth into an automatic More tab. If you have more than five destinations, either reduce the count or, on iOS 26, use TabSection to organize them into a sidebar-style structure.

Keep exploring

Skip the navigation plumbing

The Swift Kit is a $99 one-time SwiftUI boilerplate with tab navigation, onboarding, auth, and paywalls ready to ship — lifetime updates, 14-day refund.

Get The Swift Kit — $99

One-time purchase · Lifetime updates · 14-day refund