SwiftUI Sheet, Modal & Bottom Sheet — Complete 2026 Field Guide
Sheets, bottom sheets, half sheets, full-screen covers, custom modal transitions. All five SwiftUI presentation APIs in one production-ready reference.
SwiftUI has five modal APIs: .sheet (slide-up partial), .fullScreenCover (full screen), .popover (anchored on iPad), .alert/.confirmationDialog (system dialogs), and the iOS 16+ .presentationDetents which converts a sheet into a resizable bottom sheet. For most apps in 2026, target iOS 16+ to use .presentationDetents([.medium, .large]) — it replaces every third-party bottom-sheet library and matches Apple's own UX (Maps, Wallet, AirDrop).
Basic Sheet Presentation
Sheets work with two binding patterns. Boolean for "is this view showing", or Identifiable item for "show a sheet with this data". Prefer the item form when the sheet needs data to render — it avoids the optional unwrapping dance.
struct ContentView: View {
@State private var selectedTask: Task?
var body: some View {
List(tasks) { task in
Button(task.title) { selectedTask = task }
}
.sheet(item: $selectedTask) { task in
TaskDetailView(task: task)
}
}
}Bottom Sheets with .presentationDetents
The iOS 16+ presentationDetents modifier converts any sheet into a resizable bottom sheet. Pass an array of heights — users can drag between them. This is the same API that powers Maps and Wallet.
- .medium = ~50% of screen, .large = full sheet
- .fraction(0.3) for custom heights (iOS 16+)
- .height(200) for fixed-point heights
- .presentationDragIndicator(.visible) shows the grab handle
- .presentationBackgroundInteraction(.enabled(upThrough: .medium)) lets users tap behind the sheet at smaller detents
.sheet(isPresented: $showFilters) {
FiltersView()
.presentationDetents([.fraction(0.3), .medium, .large])
.presentationDragIndicator(.visible)
.presentationBackgroundInteraction(.enabled(upThrough: .medium))
}Full-Screen Covers (Onboarding, Paywalls)
.fullScreenCover takes over the entire window and requires programmatic dismissal. Use it for onboarding flows, paywalls, and any state where the user MUST complete or explicitly close. Sheets are for non-blocking UX.
.fullScreenCover(isPresented: $showPaywall) {
PaywallView()
.interactiveDismissDisabled(!hasFreeTrialEnded)
}Common Bugs and Fixes
Three modal bugs you'll hit in production:
- Sheet ignores @State changes — bind to @State on the parent, never inside the sheet content
- Detents jump on first present — set .presentationDetents BEFORE the sheet first appears
- Sheet inside NavigationStack loses navigation context — wrap the sheet content in its own NavigationStack
- Keyboard pushes sheet content offscreen — use .presentationContentInteraction(.resizes) or scroll inside
Paywall and onboarding sheets — pre-built.
The Swift Kit ships production paywall sheets and 3-style onboarding bottom sheets wired to RevenueCat and the design system.
Frequently Asked Questions
How do I present a sheet in SwiftUI?
How do I create a bottom sheet in SwiftUI?
What's the difference between sheet and fullScreenCover?
How do I disable interactive dismissal of a SwiftUI sheet?
How do I make a half-sheet that can resize?
Does The Swift Kit include sheet templates?
Keep exploring
Ship your iOS app 10× faster
The Swift Kit gives you a production-ready SwiftUI boilerplate — design system, paywall, auth, AI, all pre-wired. $99 one-time.
Get The Swift Kit — $99One-time purchase · Lifetime updates · 14-day refund