Pattern · Tab Bars

New

Action Tab Bar SwiftUI Block

A floating tab bar with a round create button set apart that opens quick actions over a scrim, with badges and tap again to scroll up.

Four tabs in one floating capsule, the selected one on a quiet pill, with a round accent create button beside it; each tab is a symbol over its label with an optional badge. The create button opens a menu of quick actions above the bar over a scrim, runs the one you pick and shows working, done and failed itself. Tapping the selected tab again calls your closure to scroll it to the top.

  • SwiftUI
  • iOS 17.0+
  • action-tab-bar
Get SwiftPieces Pro

$199 once, every piece included. See how installing works · Sign in

01

What's included

SwiftUI source you own, built on Apple frameworks only.

  • Four tabs around a central create button, generic over your own tab enum
  • A quick-create menu above the bar over a scrim, closing on pick or tap outside
  • The create button shows working, done and failed for the action it ran
  • Badge counts on tabs and tap again to scroll to top
  • Tabs keep their state once visited, as in TabView

02

Implementation notes

How it behaves, what it handles, and the decisions behind it.

Four tabs float in one capsule at the foot of the screen, and a round create button in the accent stands apart beside it, the way iOS 26 apps set a primary action off from their tabs. Each tab is a symbol over its label; the selected one sits on a quiet pill that slides between tabs and turns to its fill variant in textPrimary, and a badge count sits on the symbol. Tapping the selected tab again calls onReselect, where the app scrolls that tab to its top. The create button opens a short menu of quick actions on a raised card just above the bar, while a scrim dims the content behind it and the plus turns to a cross. Picking an action closes the menu and runs your closure: the button shows a spinner while it runs and a checkmark after work that took a moment, and a thrown error shows its message on a small card above the bar. Tapping the scrim, another tab or the button again closes the menu. With one action the button runs it directly; with none it is hidden. Tabs keep their content alive once visited, as TabView does.

Notes

  • ActionTabBar(selection:items:actions:createLabel:onAction:onReselect:content:) is generic over your own tab type (any Hashable & Sendable value, usually an enum). content builds the screen for a tab.
  • A tab bar is the root of a screen, so this block owns its container rather than being a section: it lays your content out full size, floats the bar over its foot with safeAreaInset(edge: .bottom) so your scroll views end above it, and draws the scrim over the content only. Put a NavigationStack inside each tab's content, not around the bar.
  • ActionTabBarItem(_:title:symbol:badge:) (also ActionTabBar.Item) is a tab: your value, its label, an outline SF Symbol and an optional count. Over 99 reads "99+"; nil or 0 hides it. Two to four tabs fit the capsule; the create button sits after them.
  • ActionTabBarAction(id:title:detail:symbol:) (also ActionTabBar.Action) is a menu row: a symbol, a title and an optional line under it. ActionTabBarAction.samples is the Example's menu.
  • onAction is async throws. Present a composer and return, and the button settles at once; run longer work (an upload, a scan) in it and the button shows progress, then a checkmark. A thrown error's errorDescription (or "New task didn't finish. Try again.") shows above the bar for a few seconds with a close button; CancellationError returns the button to rest quietly.
  • onReselect receives the tab tapped while already selected. The Example keeps a counter per tab and scrolls to the top with ScrollViewReader when it changes.
  • Haptics: selection when the tab changes, success when an action finishes after a moment, error when one fails.
  • VoiceOver reads each tab with its badge ("Inbox, 3 new") and the selected trait; the create button is "Create" with a hint, and reports In progress and Done. The open menu is modal and closes with the escape gesture. The bar keeps its size up to xLarge text; at accessibility sizes the labels step aside and a long press shows the system's large content viewer, as system tab bars do. Menu rows wrap their text, and their symbols step aside at accessibility sizes.
  • Reduce Motion turns the menu's rise and the button's states into short fades.
  • ActionTabBarExample at the end of ActionTabBar.swift is a planner's root: Today, Inbox with three unread, Projects and Profile, and a menu with New task, New note, Scan document (which takes a moment and confirms) and New event. Pass wrapsInNavigationStack: false when each tab already sits in a stack you own.

Theming

  • Reads SPTheme from @Environment(\.spTheme) and requires the design-system item.
  • The capsule is color.surface with a separator hairline and the e2 shadow, and the selected tab's pill is fill. Tab labels are type.caption at medium weight; symbols and labels are textPrimary when selected and textSecondary otherwise. Badges are textPrimary capsules with surface figures.
  • The create button is the block's one accent: a circle of accent with an accentOn symbol, on the e2 shadow.
  • The menu is surfaceElevated at radius.lg with a separator hairline, rows of type.body titles and type.footnote lines in textSecondary, symbols in textSecondary, and inset hairlines. A pressed row shows fill. The menu lifts on the e3 shadow. The scrim is .ultraThinMaterial with black over it at 12% (35% in dark mode), so the screen softens rather than greying flat.
  • The failure card is surfaceElevated at radius.md, with its symbol in danger and the message in type.subhead.
  • The menu rises with motion.standard; the pill slides and states cross-fade with motion.snappy.