Skip to content
BatchhhPublic

About

Beautiful, animated alerts for iOS: UIKit and SwiftUI, async/await, Liquid Glass, accessible by default.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

CoolAlert

CI Swift 6 iOS 13+ Swift Package Manager MIT License

Animated, accessible alerts for UIKit and SwiftUI. One line to show, a chain to customize.

CoolAlert.success("Payment sent", "€42.00 is on its way to Alex.")

A success alert: Payment sent

Features

  • Nine styles: success, error, warning, info, notice, question, edit, wait, and custom. Each has its own color and an icon that draws itself in.
  • Easy syntax: one-liners for common cases, a chainable builder for everything else, and async/await when you need the answer.
  • Buttons: default, cancel and destructive roles, custom colors, target/selector, and a live countdown on the button.
  • Async actions: the button shows a spinner while the work runs. If it throws, the error appears inline and the alert shakes.
  • Input: text fields with leading icons, text views, validation that enables buttons, and return-key navigation.
  • Your own content: any UIView or SwiftUI view.
  • Timeouts and progress: auto-dismiss with a draining bar, and a progress ring with a percentage.
  • Motion: seven entrance animations, UIKit Dynamics, swipe to dismiss, and haptics.
  • Liquid Glass cards on iOS 26+, with a material fallback on earlier versions.
  • Queueing: show alerts one at a time instead of stacking them.
  • Built for everyone: VoiceOver, Dynamic Type, Reduce Motion, light and dark mode, and keyboard avoidance. Tall content scrolls in any orientation.

Installation

Swift Package Manager. In Xcode, choose File › Add Package Dependencies… and enter:

https://github.com/Batchhh/CoolAlert.git

Or add it to Package.swift:

dependencies: [
    .package(url: "https://github.com/Batchhh/CoolAlert.git", branch: "main")
],
targets: [
    .target(name: "MyApp", dependencies: ["CoolAlert"])
]

Supports iOS 13+ and needs Swift 6 (Xcode 16 or later). Xcode 26 and later can only build for iOS 15+, so iOS 13 and 14 need an app built with Xcode 16. That build leaves out Liquid Glass, which needs the iOS 26 SDK.

Quick start

import CoolAlert

CoolAlert.success("Saved")
CoolAlert.error("Upload failed", "Check your connection.", closeButton: "Got it")
CoolAlert.warning("Low storage", timeout: 3)
CoolAlert.info("New in 2.0", "Swipe left on any message to snooze it.")

For anything more, build the alert, then call show():

CoolAlert(.warning, "Delete 12 photos?", "They'll be removed from all your devices.")
    .button("Delete", role: .destructive) { deletePhotos() }
    .closeButton("Cancel")
    .show()

You don't need to keep a reference: an alert stays alive while it's on screen.

Usage

Waiting for the answer

let result = await CoolAlert(.question, "Enable notifications?")
    .button("Enable")
    .closeButton("Not now")
    .present()

switch result {
case .button(let index, let title): …   // one of your buttons, by position
case .dismissed: …                      // close button, background tap, swipe, or close()
case .timeout: …
}

Cancelling the task closes the alert.

Buttons

CoolAlert(.info, "Share photo")
    .button("Messages") { … }                                  // accent color
    .button("Mail", backgroundColor: .systemIndigo) { … }
    .button("Report", role: .destructive) { … }
    .button("Legacy", target: self, selector: #selector(legacy))
    .closeButton("Cancel")                                     // nil or .hideCloseButton() to hide it
    .appearance { $0.buttonsLayout = .automatic }              // .vertical / .horizontal / .automatic
    .show()

Async actions and validation

let alert = CoolAlert(.edit, "Sign in")
let email = alert.addTextField("Email", systemImage: "envelope")
let password = alert.addTextField("Password", systemImage: "lock")
password.isSecureTextEntry = true

alert
    .validate { email.text?.contains("@") == true && !(password.text ?? "").isEmpty }
    .button("Sign in") { try await api.signIn(email.text!, password.text!) }
    .closeButton("Cancel")
    .show()

While the action runs:

  • the button shows a spinner
  • the other buttons and the fields are disabled
  • the timeout stops

If the action throws, the alert stays open, shows the error message in red and shakes. Editing a field clears the error. You can also show an error yourself with alert.showError("Wrong code").

Text input

let name = alert.addTextField("Name", systemImage: "person")   // SF Symbol, or icon: UIImage
let notes = alert.addTextView()

CoolAlert(.edit, "Rename").textField("Name", text: file.name) { $0.clearButtonMode = .always }

Return moves to the next field. On the last field, it taps the first action button. The card stays clear of the keyboard.

Custom content

CoolAlert(.info, "Stay signed in?").customView(rememberMeRow).show()

CoolAlert(.custom(color: .systemPink), "Rate your stay")
    .icon(systemName: "heart.fill")
    .customView { StarRating() }        // any SwiftUI view
    .show()

Updating a visible alert

let alert = CoolAlert.wait("Downloading").progress(0)
alert.progress = 0.42          // the ring fills; wait alerts show "42%"
alert.message = "Almost there"
alert.close()

Timeouts

CoolAlert(.notice, "Ad break")
    .button("Skip", countdown: .seconds) { … }     // "Skip (5s)", "Skip (4s)", …
    .hideCloseButton()
    .timeout(5) { print("timed out") }
    .onDismiss { … }
    .show()

Dismissing and queueing

  • Swipe to dismiss: the user can fling an alert away when it can already be dismissed without choosing an action. Turn it off with appearance.swipeToDismiss = false.
  • Background tap: set appearance.hideWhenBackgroundViewIsTapped = true to close the alert when the user taps outside it.
  • Queueing: with CoolAlert.queuesAlerts = true, alerts appear one at a time instead of stacking.
  • Closing everything: CoolAlert.dismissAll() closes every alert on screen and clears the queue.

SwiftUI

.coolAlert(isPresented: $saved) {
    CoolAlert(.success, "Saved")
}

.coolAlert(item: $failedUpload) { upload in
    CoolAlert(.error, "Couldn't upload \(upload.name)")
}

Customization

// Every alert
CoolAlert.defaultAppearance = .init {
    $0.titleFont = UIFont(name: "AvenirNext-Bold", size: 20)!
    $0.buttonCornerRadius = 24
    $0.hideWhenBackgroundViewIsTapped = true
}

// One alert
CoolAlert(.success, "Level complete")
    .color(hex: 0x39D98A)
    .icon(UIImage(named: "trophy"), tint: .white)
    .animation(.bottomToTop)   // .topToBottom .leftToRight .rightToLeft .fade .zoom .noAnimation
    .appearance {
        $0.cardStyle = .glass
        $0.dynamicAnimatorActive = true
    }
    .show()
All Appearance properties
Group Properties
Backdrop dimOpacity, backgroundBlur, hideWhenBackgroundViewIsTapped
Card cardStyle, windowWidth, contentViewColor, contentViewBorderColor, contentViewBorderWidth, contentViewCornerRadius, shadowOpacity, margin
Icon showCircularIcon, circleSize, circleRingWidth, circleTopPosition, circleIconSize, circleBackgroundColor, animatedIcons, activityIndicatorStyle
Text titleFont, titleColor, titleMinimumScaleFactor, messageFont, messageColor, messageAlignment, maxMessageHeight
Inputs textFieldHeight, textViewHeight, fieldCornerRadius, fieldFont, focusFirstField
Buttons buttonFont, buttonHeight, buttonCornerRadius, buttonsLayout, showCloseButton, shouldAutoDismiss
Behavior dynamicAnimatorActive, swipeToDismiss, disableTapGesture, hapticFeedback, showTimeoutProgress

Margin has top, titleTop, titleToMessage, textViewBottom, buttonSpacing, textFieldSpacing, bottom and horizontal.

Accessibility

  • VoiceOver: each alert is announced when it appears, and VoiceOver focus stays inside it until it closes. Inline errors are announced too. The escape gesture closes alerts the user could otherwise dismiss.
  • Dynamic Type: the default fonts scale with the user's text size.
  • Reduce Motion: every entrance becomes a fade, and the shake is skipped.

Demo app

Open Example/CoolAlertDemo.xcodeproj and run it. Each row demonstrates one feature. Two launch arguments are useful for testing:

  • -demo <id> opens a demo at launch.
  • -selftest YES runs about 60 end-to-end checks, prints PASS/FAIL, and exits.

Contributing

Contributions are welcome. CONTRIBUTING.md covers setup, running the tests (scripts/test.sh), and the pull request process.

License

CoolAlert is available under the MIT license. See LICENSE.

About

Beautiful, animated alerts for iOS: UIKit and SwiftUI, async/await, Liquid Glass, accessible by default.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages