Skip to content
Floating Tab Bar preview
Rendered from the SwiftUI source on this page.

Floating Tab Bar

From syxUI — written for both platforms, not translated between them.

A pill tab bar whose selected item expands to reveal its label.

Navigation · SwiftUI · Flutter · iOS 17 · Flutter 3.27

  • navigation
  • tab bar
  • tabs
  • pill
  • bottom bar
  • floating

The actual source

FloatingTabBar.swift
// Floating Tab Bar · syxUI · https://syxui.dev/components/floating-tab-bar
// Free in any project. Keep this line and credit syxUI where a person can read it.

import SwiftUI

/// A pill tab bar that floats above content, with the selection indicator
/// sliding between items via `matchedGeometryEffect`.
struct FloatingTabBar: View {
    struct Item: Identifiable, Equatable {
        let id: String
        var symbol: String
        var title: String
    }

    var items: [Item] = [
        .init(id: "home", symbol: "house.fill", title: "Home"),
        .init(id: "search", symbol: "magnifyingglass", title: "Search"),
        .init(id: "saved", symbol: "bookmark.fill", title: "Saved"),
        .init(id: "profile", symbol: "person.fill", title: "You"),
    ]
    var selection: String = "home"
    var onSelect: (String) -> Void = { _ in }

    @State private var current: String = ""
    @Namespace private var namespace

    private var active: String { current.isEmpty ? selection : current }

    var body: some View {
        HStack(spacing: 4) {
            ForEach(items) { item in
                let isActive = item.id == active

                Button {
                    current = item.id
                    onSelect(item.id)
                } label: {
                    HStack(spacing: 6) {
                        Image(systemName: item.symbol)
                            .font(.system(size: 15, weight: .semibold))
                        if isActive {
                            Text(item.title)
                                .font(.system(size: 14, weight: .semibold))
                                .fixedSize()
                        }
                    }
                    .foregroundStyle(isActive ? .white : Color(white: 0.45))
                    .padding(.horizontal, isActive ? 16 : 14)
                    .frame(height: 42)
                    .background {
                        if isActive {
                            Capsule()
                                .fill(.black)
                                .matchedGeometryEffect(id: "indicator", in: namespace)
                        }
                    }
                    .contentShape(Capsule())
                }
                .buttonStyle(.plain)
                .accessibilityLabel(item.title)
                .accessibilityAddTraits(isActive ? [.isSelected, .isButton] : .isButton)
            }
        }
        .padding(5)
        .background(.regularMaterial, in: Capsule())
        .overlay(Capsule().strokeBorder(Color(white: 0.88), lineWidth: 1))
        .shadow(color: .black.opacity(0.10), radius: 18, y: 6)
        .animation(.spring(response: 0.36, dampingFraction: 0.78), value: active)
    }
}

#Preview {
    FloatingTabBar()
        .padding(30)
}
iOS 17 · No dependencies

SwiftUI note. matchedGeometryEffect moves the indicator between items. Overlay this on your content with .safeAreaInset(edge: .bottom) rather than putting it in a TabView.

Dependencies

SwiftUI
No external dependencies

Requires iOS 17

Flutter
No external dependencies

Requires Flutter 3.27