Neutral
Everyday confirmations with a quiet, neutral treatment.
SWIFTUI · OPEN SOURCE
A guide to adding thoughtful, stacked toast notifications to your SwiftUI app — with gestures, timing, and style built in.
UP AND RUNNING
Add ToastKit with Swift Package Manager, then attach a ToastCenter to the view that should present notifications.
import SwiftUI
import ToastKit
struct ContentView: View {
@State private var toasts = ToastCenter()
var body: some View {
Button("Save") {
toasts.present(.normal, title: "Changes saved")
}
.toasts(toasts)
}
}That’s it. The toast appears at the bottom of the safe area for four seconds. The default swipe actions let people delete it or set it aside for later.
In Xcode, choose File → Add Package Dependencies and enter https://github.com/DavidRicardoWilde/swiftui-toastkit.
THE MESSAGE
A ToastMessage holds its content, semantic status, duration, and optional tap and swipe actions. Use the convenience method for a simple message, or build a message when you need more control.
toasts.present(.warning,
title: "Low storage",
message: "Free up space to keep syncing."
)
let message = ToastMessage(
status: .notification,
title: "New photos",
message: "12 photos from today",
icon: .system("photo.on.rectangle"),
duration: 6
)
toasts.present(message)Everyday confirmations with a quiet, neutral treatment.
Something to review, before it becomes a failure.
Give errors a clear, recognizable visual signal.
Announcements use your app’s accent color.
Presenting a message with an id that is already visible updates and brings that toast forward. This works well for progress states such as “Syncing…” followed by “Synced”.
GIVE IT A MOMENT
.automatic uses the center’s default duration of four seconds. Pass seconds directly or use .indefinite for a message that needs an explicit response.
// Two seconds
ToastMessage(title: "Copied", duration: 2)
// Stays until dismissed
ToastMessage(title: "Sync failed", duration: .indefinite)
let toasts = ToastCenter(defaultDuration: 6)A CLEAR NEXT STEP
People can tap, swipe either direction, or press and hold to see both swipe choices before deciding. VoiceOver exposes the same actions by name.
| Gesture | Default | Customize with |
|---|---|---|
| Tap | No action; taps pass through | tapAction or onTap |
| Leading swipe | Delete the toast | swipeActions.leading |
| Trailing swipe | Bring it back next visit | swipeActions.trailing |
| Press & hold | Preview both swipe actions | Uses the same swipe actions |
let archive = ToastAction(
id: "archive",
title: "Archive",
icon: .system("archivebox"),
confirmation: "Archived"
) { toast in
store.archive(toast.id)
}
toasts.present(ToastMessage(
title: "Message from Ana",
tapAction: ToastAction(title: "Open") { toast in
router.open(toast.id)
},
swipeActions: ToastSwipeActions(leading: archive, trailing: .later)
))Actions run in order: their handler, the stack’s onToastAction observer, then their effect. Built-in .later postpones the toast until its page appears again.
MAKE IT YOURS
The default .glass style draws a frosted card tinted by status. Tune design tokens with ToastTheme, choose the unstyled .plain style, or conform to ToastStyle for a custom card.
MyScreen()
.toasts(toasts)
.toastTheme(ToastTheme(
titleFont: .headline,
tint: { status in
status == .notification ? .pink : ToastTheme.standardTint(status)
},
cornerRadius: 16
))
// Start with no surface or fixed colors
MyScreen().toasts(toasts).toastStyle(.plain)A custom style receives the toast’s icon, title, message, status, and whether it is lifted onto the long-press track. Layout and gestures remain with the stack.
RIGHT WHERE IT BELONGS
By default, cards hang from the bottom and use 60% of the available safe-area width.
// Top edge
MyScreen().toasts(toasts, placement: .top)
// 90% of the safe area, or a fixed width
MyScreen().toasts(toasts, width: .fraction(0.9))
MyScreen().toasts(toasts, width: .points(320))Inside a TabView, attach .toasts(toasts) to the tab’s content. System tab bars and accessories are already part of its safe area. Mark a custom floating bar with .toastObstruction() so the stack can lift clear of it.
BUILT FOR EVERYONE
ToastKit keeps actions available without requiring a gesture and follows the accessibility settings in SwiftUI’s environment.
SPEAK THEIR LANGUAGE
ToastKit ships its built-in action labels in English, Japanese, Simplified Chinese, and Traditional Chinese. Your app’s strings are separate: localize the title, message, and any custom action labels you provide.
// Keep app copy in your String Catalog or Localizable.strings.
toasts.present(.warning,
title: String(localized: "storage.low.title"),
message: String(localized: "storage.low.message")
)ToastKit follows the host app’s locale for its own built-in labels. Custom ToastAction titles and confirmations are plain strings so you can localize them with the same tools as the rest of your app.