Skip to content

Sidebar Adaptive Tabs

The same tabs as a bottom tab bar on iPhone and as a sidebar on iPad.

Sidebar Adaptive Tabs, playing
Download .swift
Origin
Original work
Found by
tab bar · sidebar · ipad · adaptive · tabs · TabView · barra de pestañas · barra lateral · ipad · adaptable · pestañas

The same tabs as a bottom tab bar on iPhone and as a sidebar people can show on iPad.

private enum SidebarAdaptiveTabsDemoPage: Hashable {
case home, explore, library, profile
var headline: String {
switch self {
case .home: "Today's picks"
case .explore: "New places"
case .library: "Saved stories"
case .profile: "Your account"
}
}
var colors: [Color] {
switch self {
case .home: [.orange, .pink]
case .explore: [.teal, .blue]
case .library: [.purple, .indigo]
case .profile: [.mint, .green]
}
}
}
struct SidebarAdaptiveTabsDemo: View {
@State private var selection = SidebarAdaptiveTabsDemoPage.home
private let tabs = [
SidebarAdaptiveTabsItem(title: "Home", systemImage: "house.fill", value: SidebarAdaptiveTabsDemoPage.home),
SidebarAdaptiveTabsItem(title: "Explore", systemImage: "safari.fill", value: .explore),
SidebarAdaptiveTabsItem(title: "Library", systemImage: "books.vertical.fill", value: .library),
SidebarAdaptiveTabsItem(title: "Profile", systemImage: "person.crop.circle.fill", value: .profile),
]
var body: some View {
SidebarAdaptiveTabs(selection: $selection, tabs: tabs) { item in
ZStack {
LinearGradient(colors: item.value.colors, startPoint: .topLeading, endPoint: .bottomTrailing)
.ignoresSafeArea()
VStack(spacing: 16) {
Image(systemName: item.systemImage)
.font(.system(.largeTitle, design: .rounded))
.accessibilityHidden(true)
Text(item.value.headline)
.font(.system(.largeTitle, design: .rounded, weight: .bold))
.multilineTextAlignment(.center)
}
.foregroundStyle(.white)
.padding()
}
// Before iOS 26 the bar is clear over a page that does not scroll, and its items take the page's color.
.toolbarBackground(.visible, for: .tabBar)
}
}
}
ParameterTypeDefault
selectionBinding<Value>Required
tabs[SidebarAdaptiveTabsItem<Value>]Required
content(SidebarAdaptiveTabsItem<Value>) -> ContentRequired

Copy this code into your project, or download the file.

SidebarAdaptiveTabs.swift
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors
import SwiftUI
struct SidebarAdaptiveTabsItem<Value: Hashable>: Identifiable {
let title: LocalizedStringResource
let systemImage: String
let value: Value
var id: Value { value }
}
/// The same tabs as a bottom tab bar on iPhone and as a sidebar people can show on iPad.
struct SidebarAdaptiveTabs<Value: Hashable, Content: View>: View {
@Binding var selection: Value
let tabs: [SidebarAdaptiveTabsItem<Value>]
@ViewBuilder let content: (SidebarAdaptiveTabsItem<Value>) -> Content
var body: some View {
TabView(selection: $selection) {
ForEach(tabs) { item in
// The initializer that takes the title needs iOS 26 for a localizable one.
Tab(value: item.value) {
content(item)
} label: {
Label { Text(item.title) } icon: { Image(systemName: item.systemImage) }
}
}
}
.tabViewStyle(.sidebarAdaptable)
}
}
// MARK: - Demo
private enum SidebarAdaptiveTabsDemoPage: Hashable {
case home, explore, library, profile
var headline: String {
switch self {
case .home: "Today's picks"
case .explore: "New places"
case .library: "Saved stories"
case .profile: "Your account"
}
}
var colors: [Color] {
switch self {
case .home: [.orange, .pink]
case .explore: [.teal, .blue]
case .library: [.purple, .indigo]
case .profile: [.mint, .green]
}
}
}
struct SidebarAdaptiveTabsDemo: View {
@State private var selection = SidebarAdaptiveTabsDemoPage.home
private let tabs = [
SidebarAdaptiveTabsItem(title: "Home", systemImage: "house.fill", value: SidebarAdaptiveTabsDemoPage.home),
SidebarAdaptiveTabsItem(title: "Explore", systemImage: "safari.fill", value: .explore),
SidebarAdaptiveTabsItem(title: "Library", systemImage: "books.vertical.fill", value: .library),
SidebarAdaptiveTabsItem(title: "Profile", systemImage: "person.crop.circle.fill", value: .profile),
]
var body: some View {
SidebarAdaptiveTabs(selection: $selection, tabs: tabs) { item in
ZStack {
LinearGradient(colors: item.value.colors, startPoint: .topLeading, endPoint: .bottomTrailing)
.ignoresSafeArea()
VStack(spacing: 16) {
Image(systemName: item.systemImage)
.font(.system(.largeTitle, design: .rounded))
.accessibilityHidden(true)
Text(item.value.headline)
.font(.system(.largeTitle, design: .rounded, weight: .bold))
.multilineTextAlignment(.center)
}
.foregroundStyle(.white)
.padding()
}
// Before iOS 26 the bar is clear over a page that does not scroll, and its items take the page's color.
.toolbarBackground(.visible, for: .tabBar)
}
}
}
#Preview {
SidebarAdaptiveTabsDemo()
}