SwiftUI.cc
Snippets
ControlsActionsBeginner

SwiftUI Link and the openURL Environment

Link opens URLs in the appropriate app or browser; the openURL environment action does the same programmatically, and openURL can be overridden to intercept taps.

3 min readUpdated 2026-06
Support links with custom handling
import SwiftUI

struct SupportLinks: View {
    @Environment(\.openURL) private var openURL

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Link("Documentation",
                 destination: URL(string: "https://example.com/docs")!)

            Link(destination: URL(string: "mailto:help@example.com")!) {
                Label("Email Support", systemImage: "envelope")
            }

            Button("Rate the App") {
                openURL(URL(string: "https://example.com/review")!)
            }
        }
        .padding()
    }
}

Link preview

A button with a fixed destiny

Link("Docs", destination: url) renders like a button but its action is built in: hand the URL to the system, which routes it to Safari, Mail (mailto:), Phone (tel:), or any app claiming the scheme via universal links.

The programmatic twin

@Environment(\.openURL) private var openURL

openURL(receiptURL) { accepted in
    if !accepted { showFallback = true }
}

Use it after async work (open the invoice once generated) or from contexts where no visible link exists.

Intercepting opens

Because openURL is an environment value, parents can replace it:

.environment(\.openURL, OpenURLAction { url in
    showInAppBrowser(url)
    return .handled        // or .systemAction to pass through
})

This catches taps from Link and Markdown links inside Text, centralizing policy — analytics, in-app Safari, scheme allowlists — in one place.

Common mistakes

  • Using Link for actions that do not open URLs, breaking the visual contract.
  • Force-unwrapping URL(string:) on user input.
  • Forgetting that Markdown links in Text also route through openURL — handle both consistently.

Related reference