Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

97 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UnionToast

UnionToast Demo

UnionToast gives SwiftUI apps an iOS 26-style toast that feels native out of the box while staying fully customizable.

Features

  • SwiftUI .toast modifiers for both boolean bindings and identifiable items, each with onDismiss
  • Dynamic Island toast style that animates from the island on supported devices
  • System-style replacement choreography tuned for iOS 26 (liquid glass) and iOS 18 (material fade)
  • Smooth entrance and swipe-to-dismiss animations with automatic timer pause on interaction
  • Automatic liquid glass background on iOS 26 and .regularMaterial fallback elsewhere, customizable via .toastBackground
  • Overlay window architecture to keep presentation independent from the view hierarchy
  • Imperative ToastController APIs, including item-based presentations and replacement support

Installation

Add the package with Swift Package Manager:

.package(url: "https://github.com/unionst/union-toast.git", from: "1.0.0")

Then add UnionToast to the target dependencies that should show toasts.

Usage

@State private var isShowingToast = false

Button("Show Toast") { isShowingToast = true }
    .toast(isPresented: $isShowingToast) {
        Label("Invitation sent", systemImage: "paperplane.fill")
    }

Boolean modifier

import UnionToast

struct ContentView: View {
    @State private var isShowingToast = false

    var body: some View {
        Button("Send Invitation") {
            isShowingToast = true
        }
        .toast(isPresented: $isShowingToast, onDismiss: handleToastDismiss) {
            Label("Invitation sent", systemImage: "paperplane.fill")
                .font(.headline)
        }
    }

    private func handleToastDismiss() {
        // Update analytics or state when the toast completes
    }
}

The modifier applies the default styling automatically. To change the auto-dismiss timing:

.toast(isPresented: $isShowingToast, dismissDelay: .seconds(3)) {
    Text("Quick update")
        .font(.headline)
}

Item-based modifier

Use the item overload when each toast corresponds to a distinct piece of data. Identifiable & Equatable items participate in the system replacement choreography automatically.

struct AlertToast: Identifiable, Equatable {
    var id: UUID = .init()
    var title: String
    var message: String
}

struct ItemToastExample: View {
    @State private var activeToast: AlertToast?

    var body: some View {
        VStack(spacing: 16) {
            Button("Show success") {
                activeToast = AlertToast(title: "Success", message: "This will auto-dismiss.")
            }

            Button("Show warning") {
                activeToast = AlertToast(title: "Warning", message: "Replacing any visible toast.")
            }
        }
        .toast(item: $activeToast, onDismiss: handleDismiss) { toast in
            VStack(alignment: .leading, spacing: 4) {
                Text(toast.title).font(.headline)
                Text(toast.message).font(.subheadline)
            }
        }
    }

    private func handleDismiss() {
        // Clear additional state if needed
    }
}

Imperative API

ToastController exposes the same behavior for callers that prefer to trigger toasts programmatically (for example from services or reducers).

import UnionToast

func presentDownloadFinishedToast() {
    ToastController.show(dismissDelay: .seconds(4)) {
        Label("Download complete", systemImage: "checkmark.circle.fill")
            .font(.headline)
    }
}

func presentAccountToast(account: Account) {
    ToastController.show(item: account, dismissDelay: .seconds(5)) { account in
        VStack(alignment: .leading) {
            Text("Signed in")
                .font(.headline)
            Text(account.email)
                .font(.subheadline)
        }
    }
}

Customizing the background

Override the default appearance by setting a toast background configuration anywhere in the view hierarchy. This affects the environment for both modifier and controller-driven toasts.

struct CustomStylingView: View {
    var body: some View {
        content
            .toastBackground(
                .ultraThinMaterial,
                shape: RoundedRectangle(cornerRadius: 20, style: .continuous),
                glassEffect: .disabled,
                stroke: .white.opacity(0.15),
                padding: EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)
            )
    }

    private var content: some View {
        // Your app content
    }
}

On iOS 26 the default glassEffect resolves to .regular, so the toast card appears with the liquid glass aesthetic. Set glassEffect: .clear for a lighter variant or .disabled to fall back to the provided background style.

Dynamic Island style

On devices with a Dynamic Island, toasts can animate directly from the island for a more integrated feel. On devices without a Dynamic Island, the toast automatically falls back to the regular style.

Dynamic Island Toast Demo

.toast(isPresented: $showToast, style: .dynamicIsland) {
    Label("Copied to clipboard", systemImage: "doc.on.doc.fill")
}

The Dynamic Island style uses a 3-second default dismiss delay (versus 6.5 seconds for regular toasts) and supports swipe-up to dismiss.

Behavior & defaults

  • Toasts auto-dismiss after 6.5 seconds unless a custom dismissDelay is provided.
  • Interacting with the toast (hold or drag) pauses the timer; releasing resumes it.
  • Swiping up dismisses the toast with the same blur/squish animation used by the auto-dismiss path.
  • When a new toast arrives while one is on screen, the existing toast animates out while the new one animates in, matching system notification behavior.
  • Item-based presentations automatically ignore duplicate items that repeat within ~1 second to avoid accidental double posts.
  • Overlay windows are automatically created per scene, so toasts stay above modals and sheets without additional setup.

Public API

  • View.toast(isPresented:style:onDismiss:dismissDelay:content:)
  • View.toast(item:style:onDismiss:dismissDelay:content:) (Item: Identifiable & Equatable)
  • View.toastBackground(alignment:content:) and View.toastBackground(_:) for ShapeStyle backgrounds
  • ToastController.show(dismissDelay:content:)
  • ToastController.show(item:dismissDelay:onDismiss:content:)
  • ToastController.showWithHaptic(dismissDelay:haptic:content:)
  • ToastController.dismiss() / ToastController.remove()

Requirements

  • iOS 18.0 or newer
  • Swift 6.1 or newer
  • Xcode 16 or newer

Dependencies

About

Native-feeling iOS toasts

Resources

Stars

50 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages