Skip to content

Repository files navigation

ToastMessage

A lightweight, fully-customizable toast / notification library for iOS, built with SwiftUI and iOS 26's Liquid Glass.

Show a compact message — icon + title + message + action button — above any window, from anywhere in your app (SwiftUI, view models, or UIKit). Ships with semantic presets and deep customization.

Success toast

Platform Swift SPM License


Features

  • 🎯 Present from anywhere — floats above every window on its own passthrough UIWindow; touches outside the toast pass straight through to your app.
  • 🧩 Semantic presets.success, .error, .info, .warning, plus fully custom.
  • 🎨 Deep customization — background, corner radius, fonts, colors, icon, shadow, padding, max width.
  • 🪟 iOS 26 Liquid Glass — opt-in glass for the action button and/or the whole toast, each with an optional tint.
  • 👆 Four ways to dismiss — auto after a duration, swipe, tap, or the action button (with a callback). The auto-dismiss timer pauses while you're dragging.
  • 🔀 Sequential queue — multiple toasts show one at a time, in order.
  • 🧵 Three APIs — global Toast facade, a SwiftUI .toast(…) modifier, and a UIViewController bridge.
  • ♿️ Accessible — Dynamic Type, VoiceOver announcements, Reduce Motion fallback, 44 pt tap targets.
  • 🌓 Adaptive — soft spring in / smooth glide out, light & dark aware defaults.

Requirements

iOS 26.0+
Swift 6.0+ (Swift 6 language mode)
Xcode 26+

Installation

Swift Package Manager (Xcode)

File ▸ Add Package Dependencies… and enter:

https://github.com/kushal211/ToastMessage.git

Swift Package Manager (Package.swift)

dependencies: [
    .package(url: "https://github.com/kushal211/ToastMessage.git", from: "1.0.0")
],
targets: [
    .target(name: "YourApp", dependencies: ["ToastMessage"])
]

Then import ToastMessage.

Quick start

import ToastMessage

// Semantic preset with an action + callback
Toast.success("Kushal's Request has been completed",
              action: .init("Undo") { viewModel.undo() })

Toast.error("Something went wrong", message: "Please try again")
Toast.info("Heads up", message: "You have 3 new messages")
Toast.warning("Low storage", message: "Less than 1 GB remaining")

That's it — no setup, no view to install. The library manages its own window.

Presets

Success Toast.success(_:message:action:)
Error Toast.error(_:message:action:)
Info Toast.info(_:message:action:)
Warning Toast.warning(_:message:action:)

Each preset ships a distinct deep-tinted background (green / red / blue / amber) and matching icon, so they're recognizable at a glance. Every preset accepts optional style: and configuration: overrides.

Liquid Glass (iOS 26)

Opt any toast into Liquid Glass — the button, the whole background, or both — each with an optional tint.

Glass button

Glass button

Toast.success("Uploaded",
              action: .init("Undo") {  },
              style: .custom {
                  $0.buttonUsesGlass = true    // Liquid Glass button
                  $0.buttonTint = .blue         // optional; nil = plain glass
              })

Glass background

Glass background

Toast.show(
    content: ToastContent(icon: .systemImage("sparkles", tint: .yellow),
                          title: "Liquid Glass",
                          message: "The whole surface is glass with a tint.",
                          action: .init("Undo") {  }),
    style: .custom {
        $0.backgroundUsesGlass = true          // whole toast = glass
        $0.backgroundTint = .blue               // optional tint
        $0.buttonUsesGlass = true               // (independent of the background)
        $0.shadow = .none                        // glass has its own depth
    })

Customization

Style is split into three orthogonal value types, so you only set what you want to change.

ToastContentwhat is shown

Field Type Notes
icon ToastIcon? .systemImage("bell", tint:), .image(Image, tint:), or .none
title String? Primary line
message String? Secondary line
action ToastAction? Trailing button

ToastStylehow it looks

Field Default
backgroundColor near-black
backgroundUsesGlass / backgroundTint false / nil
cornerRadius 32
iconColor nil (uses the icon's own tint)
titleFont / titleColor 17 semibold / white
messageFont / messageColor 14 regular / white 70%
buttonFont / buttonForegroundColor / buttonBackgroundColor 16 semibold / white / white 14%
buttonUsesGlass / buttonTint false / nil
shadow soft drop shadow (.default, .none, or custom)
contentPadding roomy for text; auto-tightened next to a button
maxWidth nil (hugs content)

Build one with the .custom { … } builder or a preset, or the memberwise ToastStyle(…) initializer.

ToastConfigurationhow it behaves

Field Default
edge .top (or .bottom)
duration .seconds(3) (or .indefinite)
dismissOnTap true
dismissOnSwipe true
hapticFeedback nil (.success / .warning / .error / .light / .medium / .heavy)

Fully custom example — every ToastStyle property

Fully custom

Toast.show(
    content: ToastContent(
        icon: .systemImage("sparkles", tint: .yellow),
        title: "Fully Custom Toast",
        message: "Every ToastStyle field is set here.",
        action: ToastAction("Undo", dismissesToast: true) { viewModel.undo() }
    ),
    style: .custom { s in
        s.backgroundColor        = Color(red: 0.12, green: 0.10, blue: 0.24)
        s.backgroundUsesGlass    = false        // set true for a glass surface
        s.backgroundTint         = nil           // used only when backgroundUsesGlass
        s.cornerRadius           = 22
        s.iconColor              = .yellow       // overrides the icon's own tint
        s.titleFont              = .system(size: 18, weight: .bold, design: .rounded)
        s.titleColor             = .white
        s.messageFont            = .system(size: 13, weight: .regular)
        s.messageColor           = .white.opacity(0.65)
        s.buttonFont             = .system(size: 15, weight: .semibold)
        s.buttonForegroundColor  = .white
        s.buttonBackgroundColor  = .white.opacity(0.18)  // used when buttonUsesGlass is false
        s.buttonUsesGlass        = false        // set true for a glass button
        s.buttonTint             = .purple       // used only when buttonUsesGlass
        s.shadow                 = ToastShadow(color: .black.opacity(0.45), radius: 22, x: 0, y: 12)
        s.contentPadding         = EdgeInsets(top: 14, leading: 20, bottom: 14, trailing: 16)
        s.maxWidth               = 380           // nil = hug content
    },
    configuration: ToastConfiguration(
        edge: .bottom,
        duration: .seconds(6),
        dismissOnTap: true,
        dismissOnSwipe: true,
        hapticFeedback: .success
    )
)

Theming the presets

The four preset styles are overridable — reassign them once (e.g. at app launch) and every Toast.success / .error / .info / .warning call across your app picks up your theme:

ToastStyle.success = .custom {
    $0.backgroundColor = .green
    $0.iconColor = .white
    $0.cornerRadius = 16
    $0.buttonUsesGlass = true
}

// Later, anywhere:
Toast.success("Saved")   // now uses your themed success style

Set these before presenting toasts and avoid mutating them concurrently — they're lightweight global appearance defaults.

Tip: define a reusable style once and reference it everywhere:

extension ToastStyle {
    static let brand = ToastStyle.custom { $0.backgroundColor = .indigo; $0.buttonUsesGlass = true }
}

Toast.show(content: ToastContent(title: "Saved"), style: .brand)

Recipes

// Message only — no icon, no title
Toast.show(content: ToastContent(message: "Copied to clipboard"))

// A custom SwiftUI Image as the icon
Toast.show(content: ToastContent(
    icon: .image(Image("Logo"), tint: .pink),
    title: "You earned a reward!"))

// Colored background + capped width (long text wraps early)
Toast.show(
    content: ToastContent(icon: .systemImage("wifi.slash"),
                          title: "You're offline",
                          message: "We'll retry automatically when the connection returns."),
    style: .custom { $0.backgroundColor = Color(white: 0.2); $0.iconColor = .white; $0.maxWidth = 320 })

// Persistent, action-only (won't auto-dismiss, no tap/swipe to dismiss)
Toast.warning("Delete this file?",
              action: .init("Delete") { store.delete() },
              configuration: .init(duration: .indefinite, dismissOnTap: false, dismissOnSwipe: false))

// Keep the toast up after the action runs
Toast.info("Syncing…",
           action: .init("Details", dismissesToast: false) { showDetails() },
           configuration: .init(duration: .indefinite))

Presentation APIs

The same content can be presented three ways — all route through the same window/queue.

// 1. Global facade — from anywhere, incl. view models & non-view code
Toast.show(content: content, style: .brand, configuration: .init(edge: .bottom))

// 2. SwiftUI modifiers — state-driven
someView.toast(isPresented: $flag) { ToastContent(title: "Done") }
someView.toast(item: $toastContent)   // fires when the binding becomes non-nil

// 3. UIKit
viewController.showToast(content, style: .brand)
viewController.showSuccessToast("Saved")   // + error/info/warning

Dismissal & queueing

  • Auto: disappears after configuration.duration (paused while a finger is down); .indefinite never auto-dismisses.
  • Swipe / tap: enabled via dismissOnSwipe / dismissOnTap.
  • Action button: runs its handler, then dismisses (unless dismissesToast: false).
  • Programmatic: Toast.dismiss() (current) or Toast.dismissAll() (current + queued).
  • Queue: toasts shown in quick succession appear one at a time, in order.

Accessibility

  • Title, message, and button scale with Dynamic Type.
  • A new toast posts a VoiceOver announcement; the action button is a proper accessibility action.
  • Reduce Motion replaces the slide + spring with a gentle cross-fade.
  • The action button meets the 44 pt minimum tap target.

Demo

The Example/ app is a gallery that exercises every feature — presets, positions, durations, Liquid Glass, a fully custom toast, and both SwiftUI modifiers. Open Example/ToastMessage.xcodeproj and run.

Contributing

Contributions are welcome — bug reports, feature requests, and pull requests all help. Please read CONTRIBUTING.md before opening a PR for the local build/test workflow, coding conventions, and how changes are reviewed.

Quick version:

git clone https://github.com/kushal211/ToastMessage.git
cd ToastMessage
swift build      # build the package
swift test       # run the test suite

Found a bug or have an idea? Open an issue.

Author

Kushal Panchal

License

ToastMessage is available under the MIT license. See LICENSE for details.

Copyright © 2026 Kushal Panchal.

About

A lightweight, fully-customizable toast / notification library for iOS, built with SwiftUI and iOS 26's Liquid Glass.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages