SwiftUI.cc
Patterns
PatternNavigationIntermediate

SwiftUI Custom Tab Bar with Badge

A custom tab bar with animated badge indicators for unread notifications or messages.

Updated 2026-06
Custom Tab Bar with Animated Badge in Messages Tab
import SwiftUI

struct CustomTabBar: View {
    @State private var selectedTab = 0
    
    var body: some View {
        TabView(selection: $selectedTab) {
            HomeView()
                .tabItem {
                    Image(systemName: "house")
                    Text("Home")
                }
                .tag(0)
            
            MessagesView(badgeCount: 3)
                .tabItem {
                    Image(systemName: "message")
                    Text("Messages")
                }
                .tag(1)
            
            SettingsView()
                .tabItem {
                    Image(systemName: "gear")
                    Text("Settings")
                }
                .tag(2)
        }
        .accentColor(.blue)
    }
}

struct MessagesView: View {
    var badgeCount: Int
    
    var body: some View {
        NavigationView {
            Text("Messages Screen")
                .navigationTitle("Messages")
                .overlay(
                    Group {
                        if badgeCount > 0 {
                            ZStack {
                                Circle()
                                    .fill(Color.red)
                                    .frame(width: 24, height: 24)
                                Text("$badgeCount)")
                                    .foregroundColor(.white)
                                    .font(.caption)
                                    .padding(.horizontal, 4)
                            }
                            .offset(x: 70, y: -10)
                            .transition(.scale)
                            .animation(.easeInOut(duration: 0.3), value: badgeCount)
                        }
                    }
                )
        }
    }
}

#Preview {
    CustomTabBar()
}

How It Works

This implementation demonstrates a custom tab bar with an animated badge on one of the tabs. It uses TabView with a state variable selectedTab to manage the currently selected tab. Each tab item has a label and an icon, and the Messages tab includes a badge count indicating unread messages.

The badge is implemented using a ZStack that contains a red circle and a Text view showing the number of unread messages. The badge appears only when the badge count is greater than zero. The offset modifier positions the badge relative to the tab item label, and a smooth scale animation is applied using .transition(.scale) and .animation(.easeInOut(duration: 0.3), value: badgeCount) for a polished visual effect when the badge appears or updates.

The NavigationView inside the MessagesView allows for future navigation stack integration, and the overlay pattern keeps the badge visually attached to the tab bar rather than the content area. This makes the badge appear as a natural extension of the tab bar itself.

Customization

Parameter Where to change Example values
badgeCount In MessagesView initializer 0, 3, 10
Badge color .fill(Color.red) .blue, .green, .orange
Badge size .frame(width: 24, height: 24) 20, 30, 40
Animation duration .animation(.easeInOut(duration: 0.3), value: badgeCount) 0.2, 0.5, 0.7
Badge offset .offset(x: 70, y: -10) (x: 60, y: -5), (x: 80, y: -15)

Accessibility Notes

To improve accessibility, consider adding .accessibilityLabel("Unread messages: $badgeCount)") to the badge container. This ensures screen readers convey the number of unread messages to users who rely on assistive technologies.