Done and Cancel buttons in SwiftUI
SwiftUI in iOS 26 adds equivalents of UIBarButtonItem.SystemItem.done and UIBarButtonItem.SystemItem.cancel to get standard Done and Cancel buttons. This is important because to match the standard look, these buttons should show as ✔︎ and ✘ icons on iOS 26+, but they should use text labels on older versions or if UIDesignRequiresCompatibility is enabled.
Here’s the code we're using to wrap this new Button.init(role:action:) API with fallbacks for older versions:
/// Standard Done button.
/// Wrapper around `Button(role: .confirm) { }`
/// with a fallback for older versions.
struct ConfirmToolbarButton: ToolbarContent {
let action: () -> Void
var body: some ToolbarContent {
ToolbarItem(placement: .confirmationAction) {
if #available(iOS 26.0, *) {
Button(role: .confirm, action: action)
} else {
Button("Done", action: action)
}
}
}
}
/// Standard Cancel button.
/// Wrapper around `Button(role: .cancel) { }`
/// with a fallback for older versions.
struct CancelToolbarButton: ToolbarContent {
let action: () -> Void
var body: some ToolbarContent {
ToolbarItem(placement: .cancellationAction) {
if #available(iOS 26.0, *) {
Button(role: .cancel, action: action)
} else {
Button("Cancel", action: action)
}
}
}
}
Usage:
NavigationStack {
content
.navigationTitle(title)
.toolbar {
CancelToolbarButton {
// Handle cancellation
}
ConfirmToolbarButton {
// Handle confirmation
}
}
}
If your minimum version is iOS 26 or later, there’s no need to wrap these in ToolbarItem. You can use Button directly in your toolbar builder because the placement is implied by the role.