Skip to content

Instantly share code, notes, and snippets.

@hanrw
Last active March 1, 2026 19:51
Show Gist options
  • Select an option

  • Save hanrw/8d7361275878dc953b0f67ad6a21a0b5 to your computer and use it in GitHub Desktop.

Select an option

Save hanrw/8d7361275878dc953b0f67ad6a21a0b5 to your computer and use it in GitHub Desktop.
Glass Effect Feedback Toast
import SwiftUI
// MARK: - FeedbackToast
/// A dark capsule toast for transient feedback. Auto-dismisses via the `.feedbackToast` modifier.
@available(macOS 14.0, iOS 17.0, *)
public struct FeedbackToast: View {
public let icon: String
public let message: String
public let iconTint: Color
public init(icon: String, message: String, iconTint: Color = .green) {
self.icon = icon
self.message = message
self.iconTint = iconTint
}
public var body: some View {
HStack(spacing: 6) {
Image(systemName: icon)
.font(.system(size: 14))
.foregroundStyle(.white, iconTint)
Text(message)
.font(.caption.weight(.medium))
.foregroundStyle(.white)
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
.glassEffect()
.background(
Capsule()
.fill(.black.opacity(0.75))
.shadow(color: .black.opacity(0.2), radius: 4, y: 2)
)
}
}
// MARK: - View Modifier
@available(macOS 14.0, iOS 17.0, *)
private struct FeedbackToastModifier: ViewModifier {
@Binding var isPresented: Bool
let icon: String
let message: String
let iconTint: Color
let duration: TimeInterval
let edge: Edge
func body(content: Content) -> some View {
let alignment: Alignment = edge == .top ? .top : .bottom
content
.overlay(alignment: alignment) {
if isPresented {
FeedbackToast(icon: icon, message: message, iconTint: iconTint)
.padding(edge == .top ? .top : .bottom, 8)
.transition(.opacity.combined(with: .move(edge: edge)))
.onAppear {
Task {
try? await Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000))
withAnimation {
isPresented = false
}
}
}
}
}
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: isPresented)
}
}
@available(macOS 14.0, iOS 17.0, *)
public extension View {
/// Overlays a `FeedbackToast` and auto-dismisses it after `duration` seconds.
func feedbackToast(
isPresented: Binding<Bool>,
icon: String,
message: String,
iconTint: Color = .green,
duration: TimeInterval = 2.0,
edge: Edge = .top
) -> some View {
modifier(FeedbackToastModifier(
isPresented: isPresented,
icon: icon,
message: message,
iconTint: iconTint,
duration: duration,
edge: edge
))
}
}
// MARK: - Preview
@available(macOS 14.0, iOS 17.0, *)
#Preview("Variants") {
VStack(spacing: 20) {
FeedbackToast(icon: "checkmark.circle.fill", message: "Copied!")
FeedbackToast(icon: "checkmark.circle.fill", message: "Permission Revoked", iconTint: .red)
FeedbackToast(icon: "icloud.and.arrow.up.fill", message: "Saved to iCloud", iconTint: .blue)
}
.padding(40)
.background(Color.gray.opacity(0.2))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment