Glass Tab Accessory
A tab view with a Liquid Glass accessory above a tab bar that minimizes on scroll.

- Glass
- iOS 26.0+
- intermediate
- Origin
- Original work
- Found by
- tab bar · tab view · accessory · mini player · now playing · minimize · liquid glass · scroll · barra de pestañas · accesorio · minirreproductor · minimizar · cristal líquido · desplazamiento
Description
Section titled “Description”A tab view with a Liquid Glass accessory above a tab bar that minimizes on scroll.
With Reduce Motion on, the tab bar stays at its full size.
@available(iOS 26, *)private struct GlassTabAccessoryPlayer: View { @Binding var isPlaying: Bool
@Environment(\.tabViewBottomAccessoryPlacement) private var placement @Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View { HStack(spacing: 12) { Image(systemName: "waveform") .symbolEffect(.variableColor.iterative, isActive: isPlaying && !reduceMotion) .foregroundStyle(.pink) .accessibilityHidden(true)
VStack(alignment: .leading, spacing: 0) { Text("Midnight Drive") .font(.subheadline.weight(.semibold)) .lineLimit(1)
if placement != .inline { Text("The Glass Hours") .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) } }
Spacer(minLength: 0)
Button { isPlaying.toggle() } label: { // The glyph is too small to hit. The padding widens what answers to a touch, // and the same padding taken back keeps the accessory as it was. Label(isPlaying ? "Pause" : "Play", systemImage: isPlaying ? "pause.fill" : "play.fill") .padding(17) .contentShape(.rect) .padding(-17) } .labelStyle(.iconOnly) .buttonStyle(.plain) } .padding(.horizontal, 16) }}
@available(iOS 26, *)struct GlassTabAccessoryDemo: View { @State private var selection = "albums" @State private var isPlaying = true
var body: some View { GlassTabAccessory(selection: $selection) { Tab("Albums", systemImage: "square.stack.fill", value: "albums") { shelf(title: "New music", colors: [.pink, .orange, .purple, .blue, .teal]) } Tab("Radio", systemImage: "dot.radiowaves.left.and.right", value: "radio") { shelf(title: "On air", colors: [.indigo, .mint, .red, .cyan, .yellow]) } } accessory: { GlassTabAccessoryPlayer(isPlaying: $isPlaying) } }
private func shelf(title: String, colors: [Color]) -> some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 150), spacing: 14)], spacing: 14) { ForEach(0..<24, id: \.self) { index in RoundedRectangle(cornerRadius: 20) .fill(colors[index % colors.count].gradient) .aspectRatio(1, contentMode: .fit) .accessibilityHidden(true) } } .padding(16) } .safeAreaInset(edge: .top, spacing: 0) { Text(title) .font(.largeTitle.bold()) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 16) .padding(.bottom, 6) .background(.bar) } }}
@available(iOS 26, *)API reference
Section titled “API reference”| Parameter | Type | Default |
|---|---|---|
selection | Binding<Selection> | Required |
minimizeBehavior | TabBarMinimizeBehavior | .onScrollDown |
accessory | () -> Accessory | Required |
Copy this code into your project, or download the file.
// SPDX-License-Identifier: MIT// Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors
import SwiftUI
/// A tab view with a Liquid Glass accessory above a tab bar that minimizes on scroll.////// With Reduce Motion on, the tab bar stays at its full size.@available(iOS 26, *)struct GlassTabAccessory<Selection: Hashable, Tabs: TabContent<Selection>, Accessory: View>: View { @Binding var selection: Selection var minimizeBehavior: TabBarMinimizeBehavior = .onScrollDown @TabContentBuilder<Selection> let tabs: () -> Tabs @ViewBuilder let accessory: () -> Accessory
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View { TabView(selection: $selection, content: tabs) .tabBarMinimizeBehavior(reduceMotion ? .never : minimizeBehavior) .tabViewBottomAccessory(content: accessory) }}
// MARK: - Demo
@available(iOS 26, *)private struct GlassTabAccessoryPlayer: View { @Binding var isPlaying: Bool
@Environment(\.tabViewBottomAccessoryPlacement) private var placement @Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View { HStack(spacing: 12) { Image(systemName: "waveform") .symbolEffect(.variableColor.iterative, isActive: isPlaying && !reduceMotion) .foregroundStyle(.pink) .accessibilityHidden(true)
VStack(alignment: .leading, spacing: 0) { Text("Midnight Drive") .font(.subheadline.weight(.semibold)) .lineLimit(1)
if placement != .inline { Text("The Glass Hours") .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) } }
Spacer(minLength: 0)
Button { isPlaying.toggle() } label: { // The glyph is too small to hit. The padding widens what answers to a touch, // and the same padding taken back keeps the accessory as it was. Label(isPlaying ? "Pause" : "Play", systemImage: isPlaying ? "pause.fill" : "play.fill") .padding(17) .contentShape(.rect) .padding(-17) } .labelStyle(.iconOnly) .buttonStyle(.plain) } .padding(.horizontal, 16) }}
@available(iOS 26, *)struct GlassTabAccessoryDemo: View { @State private var selection = "albums" @State private var isPlaying = true
var body: some View { GlassTabAccessory(selection: $selection) { Tab("Albums", systemImage: "square.stack.fill", value: "albums") { shelf(title: "New music", colors: [.pink, .orange, .purple, .blue, .teal]) } Tab("Radio", systemImage: "dot.radiowaves.left.and.right", value: "radio") { shelf(title: "On air", colors: [.indigo, .mint, .red, .cyan, .yellow]) } } accessory: { GlassTabAccessoryPlayer(isPlaying: $isPlaying) } }
private func shelf(title: String, colors: [Color]) -> some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 150), spacing: 14)], spacing: 14) { ForEach(0..<24, id: \.self) { index in RoundedRectangle(cornerRadius: 20) .fill(colors[index % colors.count].gradient) .aspectRatio(1, contentMode: .fit) .accessibilityHidden(true) } } .padding(16) } .safeAreaInset(edge: .top, spacing: 0) { Text(title) .font(.largeTitle.bold()) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 16) .padding(.bottom, 6) .background(.bar) } }}
@available(iOS 26, *)#Preview { GlassTabAccessoryDemo()}