DSModal
feedbackCentered overlay dialog with a header, scrollable content area, and footer actions. Blocks background interaction.
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
| Prop | Type | Default | Description |
|---|---|---|---|
| isPresentedreq | Binding<Bool> | — | Controls visibility. |
| titlereq | String | — | Modal heading. |
| size | DSModalSize | .md | Width constraint (.sm, .md, .lg, .full). |
| contentreq | ViewBuilder | — | Main modal content. |
| actions | ViewBuilder? | nil | Footer action buttons. |
Examples
Confirmation dialog
Delete confirmation before an irreversible action.
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.
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.
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:
For iOS-native bottom sheets with better mobile UX
For inline, non-blocking alerts
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