Hi @coastalDev
On visionOS, sheets don't dismiss when you tap outside them — that's a deliberate platform behavior, and SwiftUI doesn't expose a modifier to opt into iPadOS-style tap-outside dismissal. The Human Interface Guidelines describe a sheet on visionOS (along with macOS, tvOS, and watchOS) as "always modal," meaning people can't interact with the parent view until they explicitly dismiss it. This also matches how system presentations behave on visionOS — .alert, .confirmationDialog, .fileImporter, .photosPicker, and the share sheet are all dismissed via an explicit control rather than a tap outside. Following the same pattern in your custom sheet will feel native.
For a data-entry Form like yours, consider pairing a Cancel button with a Done (or Save) button as text buttons in the sheet's toolbar, as many system apps do. The Best practices section of the HIG codifies this pairing — "The Cancel (or Close) button dismisses a sheet without saving any changes… The Done button dismisses a sheet after completing a task or explicitly saving changes," and further advises that "if you provide a Done button, always pair it with a Cancel button to give people a clear way to dismiss the sheet without confirming or saving their changes."
Consider wrapping the sheet's content in a NavigationStack so the .toolbar has a host, and using .cancellationAction and .confirmationAction placements so the buttons land in the right spot on every platform:
struct ContentView: View {
@State private var isAddingItem = false
var body: some View {
Button("Add item") { isAddingItem = true }
.sheet(isPresented: $isAddingItem) {
NavigationStack {
Form {
// your fields
}
.navigationTitle("New Item")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { isAddingItem = false }
}
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
// save…
isAddingItem = false
}
}
}
}
}
}
}