Modal

A fluid modal dialog component with spring animations, customizable positions, overlay styles, and drag-to-dismiss support.

Import

import PrettyUI

Basic Usage

@State private var showModal = false

Button("Show Modal") {
    showModal = true
}
.pModal(isPresented: $showModal) {
    PModalContent("Welcome!")
        .description("Thanks for using our app.")
        .icon("hand.wave.fill")
}

Positions

Show modals at different screen positions:

// Center (default)
.pModal(isPresented: $show, position: .center) { ... }

// Top
.pModal(isPresented: $show, position: .top, topPadding: 64) { ... }

// Bottom
.pModal(isPresented: $show, position: .bottom, bottomPadding: 32) { ... }

Overlay Styles

Customize the backdrop:

// Dimmed (default)
.pModal(isPresented: $show, overlay: .dimmed) { ... }

// Blurred
.pModal(isPresented: $show, overlay: .blurred(radius: 10)) { ... }

// Dimmed + Blurred
.pModal(isPresented: $show, overlay: .dimmedBlur(opacity: 0.3, radius: 10)) { ... }

// Custom color
.pModal(isPresented: $show, overlay: .color(.purple, opacity: 0.3)) { ... }

// No overlay
.pModal(isPresented: $show, overlay: .none) { ... }

Variants

Set the modal variant to change icon colors:

// Standard (default)
PModalContent("Settings")
    .variant(.standard)

// Destructive (red)
PModalContent("Delete Account")
    .variant(.destructive)

// Success (green)
PModalContent("Success!")
    .variant(.success)

// Warning (orange)
PModalContent("Warning")
    .variant(.warning)

With Actions

Add action buttons to your modal:

PModalContent("Confirm Action")
    .description("Are you sure you want to proceed?")
    .icon("exclamationmark.circle")
    .actions {
        HStack(spacing: 12) {
            PButton("Cancel") {
                showModal = false
            }
            .variant(.secondary)
            .fullWidth()
            
            PButton("Confirm") {
                performAction()
                showModal = false
            }
            .variant(.primary)
            .fullWidth()
        }
    }

Alert-Style Modal

Use the convenience modifier for common alert patterns:

.pModalAlert(
    isPresented: $showAlert,
    title: "Delete Account",
    message: "This action cannot be undone.",
    icon: "trash.circle",
    variant: .destructive,
    primaryButton: .destructive("Delete") {
        deleteAccount()
    },
    secondaryButton: .cancel("Keep Account") {
        showAlert = false
    }
)

Alert Button Types

// Primary action
.primary("Confirm") { ... }

// Secondary action
.secondary("Maybe Later") { ... }

// Cancel action
.cancel("Cancel") { ... }
// or with custom title
.cancel("Keep Account") { ... }

// Destructive action
.destructive("Delete") { ... }

Dismiss Options

Background Tap

// Disable background tap dismiss
.pModal(
    isPresented: $show,
    dismissOnBackgroundTap: false
) { ... }

Close Button

PModalContent("Title")
    .showCloseButton(true)   // Show (default)
    .showCloseButton(false)  // Hide

Drag to Dismiss

Modals support drag gestures:

  • Center/Bottom: Drag down to dismiss
  • Top: Drag up to dismiss

Custom Content

Use completely custom content:

.pModal(isPresented: $showRating, position: .bottom) {
    VStack(spacing: 20) {
        Image(systemName: "star.fill")
            .font(.system(size: 48))
            .foregroundColor(.yellow)
        
        Text("Rate Our App")
            .font(.title2)
            .fontWeight(.bold)
        
        HStack(spacing: 8) {
            ForEach(1...5, id: \.self) { _ in
                Image(systemName: "star.fill")
                    .foregroundColor(.yellow)
            }
        }
        
        PButton("Submit") {
            showRating = false
        }
        .variant(.primary)
        .fullWidth()
    }
    .padding(24)
    .frame(maxWidth: 340)
    .background(Color.white)
    .clipShape(RoundedRectangle(cornerRadius: 28))
}

Real-World Examples

Delete Confirmation

.pModalAlert(
    isPresented: $showDeleteConfirm,
    title: "Remove Contact",
    message: "This contact will be removed from your address book.",
    icon: "exclamationmark.circle",
    variant: .destructive,
    primaryButton: .destructive("Remove") {
        deleteContact()
        showDeleteConfirm = false
    },
    secondaryButton: .cancel {
        showDeleteConfirm = false
    }
)

Success Confirmation

.pModal(isPresented: $showSuccess, position: .center) {
    PModalContent("Transaction Complete")
        .description("Your payment of $50.00 was successful.")
        .icon("checkmark.circle")
        .variant(.success)
        .showCloseButton(false)
        .actions {
            PButton("Done") {
                showSuccess = false
            }
            .variant(.primary)
            .fullWidth()
        }
}

Settings Sheet

.pModal(isPresented: $showSettings, position: .bottom, bottomPadding: 0) {
    PModalContent("Notification Settings")
        .description("Choose how you'd like to be notified.")
        .icon("bell.badge")
        .actions {
            VStack(spacing: 12) {
                PButton("Enable All") { ... }
                    .variant(.primary)
                    .fullWidth()
                
                PButton("Customize") { ... }
                    .variant(.outline)
                    .fullWidth()
                
                PButton("Not Now") { showSettings = false }
                    .variant(.ghost)
                    .fullWidth()
            }
        }
}

API Reference

pModal Modifier

.pModal(
    isPresented: Binding<Bool>,
    position: PModalPosition = .center,
    overlay: PModalOverlayStyle = .dimmed,
    dismissOnBackgroundTap: Bool = true,
    topPadding: CGFloat? = nil,
    bottomPadding: CGFloat? = nil,
    horizontalPadding: CGFloat = 16,
    @ViewBuilder content: () -> Content
)

pModalAlert Modifier

.pModalAlert(
    isPresented: Binding<Bool>,
    position: PModalPosition = .center,
    overlay: PModalOverlayStyle = .dimmed,
    title: String,
    message: String? = nil,
    icon: String? = nil,
    variant: PModalVariant = .standard,
    primaryButton: PModalButton,
    secondaryButton: PModalButton? = nil
)

PModalContent Modifiers

ModifierTypeDescription
.description(_:)String?Description text
.icon(_:)StringSF Symbol name
.iconColor(_:)ColorCustom icon color
.variant(_:)PModalVariantVisual variant
.showCloseButton(_:)BoolShow/hide close button
.radius(_:)RadiusSizeCorner radius
.maxWidth(_:)CGFloatMaximum width
.onClose(_:)() -> VoidClose callback
.actions(_:)ViewBuilderAction buttons

Enums

PModalPosition

  • .center — Centered (default)
  • .top — Top of screen
  • .bottom — Bottom of screen

PModalVariant

  • .standard — Neutral
  • .destructive — Red accent
  • .success — Green accent
  • .warning — Orange accent

PModalOverlayStyle

  • .dimmed — Black overlay
  • .blurred(radius:) — Blur effect
  • .dimmedBlur(opacity:radius:) — Combined
  • .color(_:opacity:) — Custom color
  • .none — Transparent