Skip to content

Accessibility Focus (SwiftUI)

A SwiftUI guide for coding agents. Also covers accessibilityfocusstate, accessibilityfocused modifier, voiceover focus move, switch control focus, assistive technology cursor.

The @AccessibilityFocusState property wrapper (SwiftUI Release 3, iOS 15+) reads and moves the VoiceOver or Switch Control cursor, mirroring how @FocusState handles the keyboard. Use it to send the assistive cursor to an error, an alert, or the first field of a form.

Boolean Bindings

Declare an @AccessibilityFocusState boolean and bind it with .accessibilityFocused($flag). SwiftUI keeps the value in sync with the assistive cursor, and writing true moves the cursor to that view.

struct SignInView: View {
  @AccessibilityFocusState
  private var isEmailFocused: Bool

  @State private var email = ""

  var body: some View {
    Form {
      TextField("email", text: $email)
        .accessibilityFocused($isEmailFocused)
    }
    .onChange(of: isEmailFocused) { newValue in
      print(newValue)
    }
  }
}

By default the value aggregates every assistive technology on the device. Scope it with @AccessibilityFocusState(for: .switchControl) or .voiceOver when only one should drive it.

One Enum for Many Elements

Like @FocusState, the wrapper accepts any Hashable value. Model the focusable elements as an enum, bind each view with .accessibilityFocused($focus, equals: .case), and keep the property optional so the framework can set nil when focus leaves your views.

enum FocusableField: Hashable {
  case email
  case password
}

struct ContentView: View {
  @AccessibilityFocusState
  private var focus: FocusableField?
  @State private var email = ""
  @State private var password = ""

  var body: some View {
    Form {
      TextField("email", text: $email)
        .accessibilityFocused($focus, equals: .email)
      SecureField("password", text: $password)
        .accessibilityFocused($focus, equals: .password)
      Button("login", action: login)
    }
    .onAppear {
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        focus = .email
      }
    }
  }
}

The article delays the initial focus = .email slightly after onAppear, so the hierarchy exists before the cursor moves. Assigning the enum value at any point moves VoiceOver or Switch Control to that element.

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-accessibility-focus" })
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