# Bars, tabs and toolbars on iOS 26 and 27

Companion to `../SKILL.md` for the `hig-components-layout` skill. Each API
carries the iOS release it first shipped in; gate anything newer than the
deployment target with `#available`. The glass material itself (variants,
custom glass, fallbacks) is in `swiftui-liquid-glass`.

## Contents

1. [Content layer and navigation layer](#1-content-layer-and-navigation-layer)
2. [Tab bars](#2-tab-bars)
3. [Search in a tab bar or toolbar](#3-search-in-a-tab-bar-or-toolbar)
4. [Toolbars and navigation bars](#4-toolbars-and-navigation-bars)
5. [Overflow, priority and pinning (iOS 27)](#5-overflow-priority-and-pinning-ios-27)
6. [Minimizing bars on scroll](#6-minimizing-bars-on-scroll)
7. [Review checklist](#7-review-checklist)

## 1. Content layer and navigation layer

From iOS 26, tab bars, toolbars, navigation bars and sidebars float in a
Liquid Glass layer above the content. Three consequences:

- **Keep navigation and content apart.** Destinations live in tab bars and
  sidebars, actions in toolbars, and content scrolls underneath. Do not put
  navigation inside content cards, or content inside bars.
- **No custom bar backgrounds.** Background images, colors and appearance
  fills on `TabView`, toolbars and navigation bars sit on top of the glass and
  break the scroll edge effect. Remove them and let content color the bar. Where
  the bar must separate more from content, adjust
  `scrollEdgeEffectStyle(_:for:)` rather than painting.
- **Tint sparingly.** Bar symbols are monochrome by default and follow the
  content beneath. Over colorful content keep them monochrome, or pick an
  accent that differs clearly from the content.

## 2. Tab bars

| API | Since | What it does |
|---|---|---|
| `Tab(_:systemImage:value:content:)` | iOS 18 | A tab; keep five or fewer by default |
| `.tabViewStyle(.sidebarAdaptable)` | iOS 18 | Tab bar on iPhone, sidebar on iPad and Mac |
| `.tabBarMinimizeBehavior(_:)` | iOS 26 | `.automatic`, `.never`, `.onScrollDown`, `.onScrollUp` |
| `.tabViewBottomAccessory { }` | iOS 26 | A persistent accessory, such as a mini player, above the tab bar |
| `.tabViewBottomAccessory(isEnabled:content:)` | iOS 26.1 | The same, shown and hidden dynamically |
| `@Environment(\.tabViewBottomAccessoryPlacement)` | iOS 26 | `.expanded` above the bar or `.inline` beside a minimized bar |
| `Tab(role: .search)` | iOS 18 | The search tab; iOS 26 sets it apart at the trailing end |
| `TabRole.prominent` | iOS 27 | Prominent treatment for one tab; without one, the search tab may get it |

```swift
TabView(selection: $selection) {
    Tab("Trips", systemImage: "airplane", value: .trips) { TripsView() }
    Tab("Wallet", systemImage: "wallet.pass", value: .wallet) { WalletView() }
        .badge(pendingPasses)
    Tab("Account", systemImage: "person.crop.circle", value: .account) { AccountView() }
    Tab(value: .search, role: .search) { SearchView() }
}
.tabViewStyle(.sidebarAdaptable)
.tabBarMinimizeBehavior(.onScrollDown)
.tabViewBottomAccessory {
    BoardingStatusBar()
}
```

Accessory content adapts to its placement:

```swift
struct BoardingStatusBar: View {
    @Environment(\.tabViewBottomAccessoryPlacement) private var placement

    var body: some View {
        if placement == .inline {
            Label("Gate B12", systemImage: "airplane.departure")
        } else {
            HStack {
                Label("Gate B12", systemImage: "airplane.departure")
                Spacer()
                Text("Boarding 09:40")
            }
            .padding(.horizontal)
        }
    }
}
```

On iOS 27, mark the one tab that deserves emphasis. With an older deployment
target, build the iOS 27 `TabView` in its own `@available(iOS 27, *)` view and
choose between the two with `if #available`.

```swift
Tab("Book", systemImage: "plus", value: .book, role: .prominent) {
    BookingFlow()
}
```

Rules:

- A tab switches context; never use a tab as an action ("New trip"). Only one
  tab can be prominent.
- Do not hide or disable tabs because of state; explain inside the tab.
- Minimize the tab bar only when reading content benefits; people restore it
  by tapping a tab or scrolling the other way.
- The accessory is for something persistent and app-wide (playback, an active
  trip), not for a screen's primary action.

## 3. Search in a tab bar or toolbar

- `Tab(role: .search)` gives search its own tab; the first tab with that role
  implements search. `tabViewSearchActivation(.searchTabSelection)` (iOS 26)
  activates search when the tab is selected and returns to the previous tab
  when search is dismissed.
- `.searchable(text:)` on a `NavigationStack` places the field where the
  platform expects; `searchToolbarBehavior(.minimize)` (iOS 26) shows the
  bottom-toolbar field on iPhone as a compact button until used.
- Scopes and suggestions for large catalogs: `.searchScopes(_:scopes:)` (iOS
  16), the `activation:` form iOS 16.4, and `.searchSuggestions`.

```swift
NavigationStack {
    LibraryList(query: query, scope: scope)
        .searchable(text: $query)
        .searchScopes($scope) {
            ForEach(LibraryScope.allCases, id: \.self) { Text($0.label).tag($0) }
        }
}
```

## 4. Toolbars and navigation bars

| API | Since | What it does |
|---|---|---|
| Semantic placements (`.primaryAction`, `.confirmationAction`, `.cancellationAction`, `.topBarLeading`, `.topBarTrailing`, `.bottomBar`) | iOS 14 / 17 SDK | Let the system position items per platform |
| `ToolbarSpacer(.fixed)` / `ToolbarSpacer(.flexible)` | iOS 26 | Splits items into separate glass groups, or pushes groups apart |
| `.sharedBackgroundVisibility(.hidden)` | iOS 26 | Takes an item out of the shared glass group |
| `Button(role: .confirm)` / `Button(role: .close)` | iOS 26 | System confirm and close buttons in a toolbar |

```swift
.toolbar {
    ToolbarItem(placement: .cancellationAction) {
        Button(role: .close) { dismiss() }
    }
    ToolbarItem { Button("Share", systemImage: "square.and.arrow.up", action: share) }
    ToolbarItem { Button("Duplicate", systemImage: "plus.square.on.square", action: duplicate) }
    ToolbarSpacer(.fixed)
    ToolbarItem(placement: .confirmationAction) {
        Button(role: .confirm) { save() }
    }
}
```

HIG guidance for toolbars:

- Prefer system symbols without borders to text; do not mix text and icons in
  one group. Every icon still gets an accessibility label.
- One primary action, on the trailing side, with the prominent treatment.
- Aim for at most three groups; group items that act on the same thing.
- Use the standard Back and Close buttons, not custom "Back" text.
- Hide a toolbar item itself rather than the view inside it, or an empty glass
  slot remains.
- Custom components inside a bar keep corner radii concentric with the bar.

## 5. Overflow, priority and pinning (iOS 27)

| API | What it does |
|---|---|
| `ToolbarOverflowMenu { }` | Actions that always live in the toolbar's overflow menu, whatever the width; on iOS it is the navigation bar's overflow menu |
| `.toolbarOverflowMenu { }` (view modifier) | Configures that overflow menu from a view |
| `.visibilityPriority(.high)` / `.low` | When space runs out, lower-priority items move to overflow first; `ToolbarItemVisibilityPriority(higherThan:)` and `(lowerThan:)` give relative ranks |
| `ToolbarItemPlacement.topBarPinnedTrailing` | Pins an item to the trailing edge of the top bar; it moves to overflow only when search is active and space is short |

```swift
.toolbar {
    ToolbarItem(placement: .topBarPinnedTrailing) {
        Button("Add", systemImage: "plus", action: add)
    }
    ToolbarItem { Button("Filter", systemImage: "line.3.horizontal.decrease", action: filter) }
        .visibilityPriority(.high)
    ToolbarItem { Button("Sort", systemImage: "arrow.up.arrow.down", action: sort) }
    ToolbarOverflowMenu {
        Button("Export", action: export)
        Button("Print", action: printList)
    }
}
```

With an older deployment target, keep this toolbar in an
`@available(iOS 27, *)` view modifier and apply it behind `if #available`,
with a plain toolbar for earlier releases. HIG says the system adds an overflow menu by itself on
iPad and Mac when items do not fit: do not hand-build a "more" `Menu` for that,
and choose priorities so the important items stay.

## 6. Minimizing bars on scroll

| API | Since | What it does |
|---|---|---|
| `.tabBarMinimizeBehavior(.onScrollDown)` | iOS 26 | Tab bar recedes while scrolling |
| `.toolbarMinimizationBehavior(_:for:)` | iOS 27 | Navigation bar minimizes on scroll; an integrated top tab bar minimizes with it. Supported placement: `.navigationBar` |
| `.toolbarMinimizationRestoration(.atScrollEdge, for:)` | iOS 27 | Restore only at the scroll edge (with `.onScrollDown`, navigation bar only) |
| `.toolbarMinimizationSafeAreaAdjustment(_:for:)` | iOS 27 | Whether the safe area follows the minimizing bar |

```swift
NavigationStack {
    ArticleView()
        .navigationTitle("Reading")
        .toolbarMinimizationBehavior(.onScrollDown, for: .navigationBar)
}
```

Use minimization for long reading or browsing surfaces, not for forms or
screens whose bar holds the main actions.

## 7. Review checklist

- [ ] No custom backgrounds on tab bars, toolbars or navigation bars
- [ ] Navigation in bars and sidebars, content in the content layer
- [ ] Tabs are destinations, five or fewer by default, at most one `.prominent` (iOS 27)
- [ ] Search uses `Tab(role: .search)` or `.searchable`, not a custom field in a bar
- [ ] Toolbar items use semantic placements, symbols with labels, one prominent action, groups split with `ToolbarSpacer`
- [ ] iOS 27 overflow, priority, pinning and minimization APIs are gated with `#available`
- [ ] Minimizing bars only on reading surfaces; accessory content adapts to `.inline`

Sources:
[HIG: Tab bars](https://developer.apple.com/design/human-interface-guidelines/tab-bars),
[HIG: Toolbars](https://developer.apple.com/design/human-interface-guidelines/toolbars),
[Adopting Liquid Glass](https://developer.apple.com/documentation/technologyoverviews/adopting-liquid-glass),
[tabBarMinimizeBehavior(_:)](https://developer.apple.com/documentation/swiftui/view/tabbarminimizebehavior(_:)),
[tabViewBottomAccessory(content:)](https://developer.apple.com/documentation/swiftui/view/tabviewbottomaccessory(content:)),
[TabRole.prominent](https://developer.apple.com/documentation/swiftui/tabrole/prominent),
[ToolbarOverflowMenu](https://developer.apple.com/documentation/swiftui/toolbaroverflowmenu),
[visibilityPriority(_:)](https://developer.apple.com/documentation/swiftui/toolbarcontent/visibilitypriority(_:)),
[topBarPinnedTrailing](https://developer.apple.com/documentation/swiftui/toolbaritemplacement/topbarpinnedtrailing),
[toolbarMinimizationBehavior(_:for:)](https://developer.apple.com/documentation/swiftui/view/toolbarminimizationbehavior(_:for:)),
[ToolbarSpacer](https://developer.apple.com/documentation/swiftui/toolbarspacer).
