Feedback/DSModal

DSModal

feedback
since v1.0

Centered overlay dialog with a header, scrollable content area, and footer actions. Blocks background interaction.

iOS 17+macOS 14+

Purpose

Confirmation dialogs, forms requiring immediate attention, and multi-step mini-flows.

Props

PropTypeDefaultDescription
isPresentedreqBinding<Bool>Controls visibility.
titlereqStringModal heading.
sizeDSModalSize.mdWidth constraint (.sm, .md, .lg, .full).
contentreqViewBuilderMain modal content.
actionsViewBuilder?nilFooter action buttons.

Examples

Confirmation dialog

Delete confirmation before an irreversible action.

swift
DSModal(isPresented: $showDelete, title: "Delete project?") {
    Text("This action cannot be undone.")
        .font(.system(size: 15))
        .foregroundStyle(DSColor.muted)
} actions: {
    HStack {
        DSButton("Cancel", variant: .ghost) { showDelete = false }
        DSButton("Delete", variant: .destructive) { viewModel.delete() }
    }
}

Form modal

Multi-field form in a modal.

swift
DSModal(isPresented: $showAddTask, title: "New Task", size: .md) {
    VStack(alignment: .leading, spacing: DSSpacing.md) {
        DSTextField(label: "Title", text: $taskTitle)
        DSTextArea(label: "Description", text: $taskDescription)
        DSSelectField(label: "Priority", selection: $priority, options: priorities)
    }
} actions: {
    HStack {
        DSButton("Cancel", variant: .ghost) { showAddTask = false }
        DSButton("Create", variant: .primary, isLoading: viewModel.isSaving) {
            viewModel.createTask()
        }
    }
}

Composition: Multi-step flow

Modal with steps and navigation.

swift
DSModal(isPresented: $showOnboarding, title: "Welcome", size: .lg) {
    VStack(spacing: DSSpacing.lg) {
        if step == 1 {
            OnboardingStep1()
        } else if step == 2 {
            OnboardingStep2()
        } else {
            OnboardingStep3()
        }
    }
} actions: {
    HStack {
        if step > 1 {
            DSButton("Back", variant: .ghost) { step -= 1 }
        }
        Spacer()
        DSButton(step == 3 ? "Finish" : "Next", variant: .primary) {
            if step < 3 { step += 1 } else { completeOnboarding() }
        }
    }
}

When to Use

✓ Use DSModal for:

  • Critical confirmations (delete, cancel subscription)
  • Forms requiring immediate attention
  • Multi-step flows that block the main UI
  • Content that needs full user focus

✗ Avoid using for:

  • Quick confirmations (use DSToast)
  • Non-blocking forms (use inline or DSBottomSheet)
  • Mobile-first actions (use DSBottomSheet on iOS)
  • Simple yes/no questions (use native .alert())

Consider instead:

DSBottomSheet

For iOS-native bottom sheets with better mobile UX

DSAlert

For inline, non-blocking alerts

DSToast

For quick confirmations that auto-dismiss

Accessibility

VoiceOver

Announces 'Modal, [title]'. Focus trapped within modal

Keyboard

  • Escape to dismiss
  • Tab cycles through interactive elements
  • Return to confirm primary action

Dynamic Type

✓ Supported

Contrast

WCAG AA compliant (overlay 3:1, content 4.5:1)

Traits

.isModalFocus trap activeBackground dimmed and non-interactive

Related Components