Skip to content

Focus Management (SwiftUI)

A SwiftUI guide for coding agents. Also covers focusable modifier, isfocused environment, tvos remote focus, prefersdefaultfocus, focus scope namespace, default focus entry point.

The focus APIs make views selectable through the tvOS remote, hardware keyboards, and the watchOS crown, and let you style the focused state. focusable and isFocused work on all Apple platforms; the default-focus APIs shown below are watchOS and tvOS.

Making Views Focusable

focusable(_:interactions:) opts a custom view into the focus system. List and Button are focusable already; do not add the modifier to them. The FocusInteractions argument declares what focus is for on that view: activate, edit, or automatic.

ScrollView(.horizontal) {
  LazyHStack {
    ForEach(0..<100) { index in
      PosterView()
        .focusable(true, interactions: .automatic)
    }
  }
}

Reacting to Focus

The isFocused environment value reports whether the nearest focusable ancestor has focus, so a child can style itself without being focusable.

struct PosterView: View {
  @Environment(\.isFocused) var isFocused

  var body: some View {
    RoundedRectangle(cornerRadius: 8)
      .frame(width: 100, height: 150)
      .scaleEffect(isFocused ? 1.2 : 1)
      .animation(.easeInOut, value: isFocused)
  }
}

focusEffectDisabled() removes the system focus ring so you can draw your own indicator from isFocused.

Default Focus (watchOS and tvOS)

prefersDefaultFocus(_:in:) names the view focus should land on first, scoped to a @Namespace that the ancestor declares with focusScope. The Bool lets the preference follow state, so an empty form focuses the field and a filled one focuses the button.

@Namespace private var namespace
@Environment(\.resetFocus) var resetFocus

VStack {
  TextField("email", text: $email)
    .prefersDefaultFocus(!hasFilledCredentials, in: namespace)
  SecureField("password", text: $password)
  Button("login") {}
    .prefersDefaultFocus(hasFilledCredentials, in: namespace)
  Button("reset credentials") {
    hasFilledCredentials = false
    resetFocus(in: namespace)
  }
}
.focusScope(namespace)

The resetFocus environment action sends focus back to the preferred default of a scope. Multiple namespaces can carve one hierarchy into separate focus scopes with their own entry points.

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-focus-management" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems