Autumn Sale50% offCOMPOT
Alerts & ModalsPro ComponentiOS 16.0+Xcode 15+

Custom Alert View

Beautifully styled custom modal alert overlay with title, message, and customizable action buttons.

SwiftUI Implementation

Pure Vanilla Swift • No Dependencies
alertBasicCustom.swift
import SwiftUI

struct UseCustomAlert: View {
    
    @State private var showAlert: Bool = true
    
    private func handleAction() {
        print("Action triggered")
    }
    
    var body: some View {
        
        VStack {
            Button("Show Alert") {
                showAlert.toggle()
            }
        }
        .customAlert(
            isPresented: $showAlert,
            title: "Permanently Delete?",
            message: "Do you really want to permanently delete this item? This action cannot be undone and all associated data will be lost.",
            icon: Image(systemName: "trash.fill"),
            iconTintColor: .red,
            primaryAction: CustomAlertAction(label: "Delete", style: .destructive) {
                handleAction()
                showAlert = false
            },
            secondaryAction: CustomAlertAction(label: "Cancel", style: .cancel) {
                showAlert = false
            }
        )
    }
}

#Preview {
    UseCustomAlert()
}

// Enum to define the style of an alert action button
enum CustomAlertActionStyle {
    case primary
    case secondary
    case destructive
    case cancel
    
    var foregroundStyle: Color {
        switch self {
        case .primary: return .accentColor
        case .secondary, .cancel: return .accentColor
        case .destructive: return .red
        }
    }
    
    var fontWeight: Font.Weight {
        switch self {
        case .primary, .destructive: return .semibold
        case .secondary, .cancel: return .regular
        }
    }
}

struct CustomAlertAction {
    let id = UUID()
    let label: String
    let style: CustomAlertActionStyle
    let action: () -> Void
}

struct CustomAlertDisplayView: View {
    let title: String
    let message: String?
    let icon: Image?
    let iconTintColor: Color
    
    // Actions
    let primaryAction: CustomAlertAction
    let secondaryAction: CustomAlertAction?
    
    private var hasOnlyOneAction: Bool { secondaryAction == nil }
    
    var body: some View {
        VStack(spacing: 0) {
            if let icon = icon {
                icon
                    .resizable()
                    .scaledToFit()
                    .frame(width: 40, height: 40)
                    .foregroundColor(iconTintColor)
                    .padding(.bottom, 10)
            }
            
            Text(title)
                .font(.system(size: 18, weight: .semibold))
                .foregroundColor(.primary)
                .multilineTextAlignment(.center)
                .padding(.bottom, message == nil && icon == nil ? 20 : 6)
            
            if let message = message, !message.isEmpty {
                Text(message)
                    .font(.system(size: 14, weight: .regular))
                    .foregroundColor(.secondary)
                    .multilineTextAlignment(.center)
                    .lineLimit(nil)
                    .fixedSize(horizontal: false, vertical: true) .padding(.horizontal)                    .padding(.bottom, 20)
            }
            
            buttonLayout
        }
        .padding(.top, 20)
        .frame(maxWidth: 300)
        .background(Material.regular)
        .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
        .shadow(color: .black.opacity(0.12), radius: 20, x: 0, y: 8)        .overlay(
            RoundedRectangle(cornerRadius: 16, style: .continuous)
                .stroke(Color.primary.opacity(0.1), lineWidth: 0.5)
        )
    }
    
    @ViewBuilder
    private var buttonLayout: some View {
        VStack(spacing: 0) {
            Divider()
            if let secondaryAction = secondaryAction {
                HStack(spacing: 0) {
                    alertButton(for: secondaryAction)
                    Divider()
                    alertButton(for: primaryAction)
                }
                .frame(height: 48)
            } else {
                alertButton(for: primaryAction)
                    .frame(height: 48)
            }
        }
    }
    
    @ViewBuilder
    private func alertButton(for alertAction: CustomAlertAction) -> some View {
        Button {
            alertAction.action()
        } label: {
            Text(alertAction.label)
                .font(.system(size: 17, weight: alertAction.style.fontWeight))
                .foregroundStyle(alertAction.style.foregroundStyle)
                .frame(maxWidth: .infinity, maxHeight: .infinity)                 .contentShape(Rectangle())
        }
    }
}

struct CustomAlertModifier: ViewModifier {
    @Binding var isPresented: Bool
    let title: String
    let message: String?
    let icon: Image?
    let iconTintColor: Color
    let primaryAction: CustomAlertAction
    let secondaryAction: CustomAlertAction?
    
    func body(content: Content) -> some View {
        ZStack {
            content
            
            if isPresented {
                Color.black.opacity(0.45)
                    .ignoresSafeArea()
                    .transition(.opacity)
                
                CustomAlertDisplayView(
                    title: title,
                    message: message,
                    icon: icon,
                    iconTintColor: iconTintColor,
                    primaryAction: primaryAction,
                    secondaryAction: secondaryAction
                )
                .padding(.horizontal, 28)
                .transition(alertTransition)
            }
        }
        .animation(.spring(response: 0.35, dampingFraction: 0.8, blendDuration: 0), value: isPresented)
    }
    
    private var alertTransition: AnyTransition {
        .asymmetric(
            insertion: .opacity.combined(with: .scale(scale: 1.1, anchor: .center)),
            removal: .opacity.combined(with: .scale(scale: 0.85, anchor: .center))
        )
    }
}

extension View {
    func customAlert(
        isPresented: Binding<Bool>,
        title: String,
        message: String? = nil,
        icon: Image? = nil,
        iconTintColor: Color = .accentColor, // Default icon tint
        primaryAction: CustomAlertAction,
        secondaryAction: CustomAlertAction? = nil
    ) -> some View {
        self.modifier(
            CustomAlertModifier(
                isPresented: isPresented,
                title: title,
                message: message,
                icon: icon,
                iconTintColor: iconTintColor,
                primaryAction: primaryAction,
                secondaryAction: secondaryAction
            )
        )
    }
}

Ready to copy and paste directly into any Xcode project running iOS 16.0+.

Recommended Use Cases

  • Dashboard overviews, stats tracking, and metric highlights

Component Details

iOS Compatibility
iOS 16.0+
Xcode Version
Xcode 15+
Platform
iOS
License Tier
Pro
Themes & Contexts
dashboard
Keywords
alert modifier
C

Compot for iOS

Native SwiftUI playground app

Preview all 130+ components interactively on your device and copy code straight to Xcode with 1 tap.

Get Compot on the App Store