How do I dismiss a presented sheet?

I'm developing an app requiring data entry across several devices. My SwiftUI app runs on iOS and iPadOS but I also want to run it on visionOS. I'm using the visionOS simulator.

When I enter data in one of my views I use a Form within a .sheet and this works perfectly well on iOS and iPadOS and I can dismiss the sheet by simply tapping the view behind the sheet.

On visionOS I click my + button, the sheet appears, I enter the data as usual but after that there is no gesture in the app I can perform with keyboard or mouse that will make the sheet disappear!

Do I have to add a "Close" button for visionOS or is there a way to enable the same interaction that works on iPadOS?

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
                            }
                        }
                    }
                }
            }
    }
}
How do I dismiss a presented sheet?
 
 
Q