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.

Interactive Reference

Live showroom

Need the full visual surface?

Screenshots do not scale well across every component, state, and variant. For the real interactive reference, import the package and launch DSShowcaseRoot().

Best for exploring:

variants, states, categories, and real app examples in one place.

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