Skip to content

FocusState (SwiftUI)

A SwiftUI guide for coding agents. Also covers focusstate, focused modifier, dismiss keyboard programmatically, move focus between fields, defaultfocus, searchfocused.

The @FocusState property wrapper reads and writes which view has focus, paired with the focused view modifier. Use it to move the cursor between fields, focus the first invalid input, or dismiss the keyboard programmatically.

Boolean Bindings

Declare a @FocusState boolean and bind it with .focused($flag). SwiftUI flips it to true when the user focuses the field and back to false when focus leaves. Writing the value moves focus: set it to true to focus the field, or false to hide the keyboard.

struct SignInView: View {
  @FocusState private var isEmailFocused: Bool
  @FocusState private var isPasswordFocused: Bool
  @State private var email = ""
  @State private var password = ""

  var body: some View {
    Form {
      TextField("email", text: $email)
        .focused($isEmailFocused)
      SecureField("password", text: $password)
        .focused($isPasswordFocused)

      Button("login") {
        if email.isEmpty {
          isEmailFocused = true
        } else if password.isEmpty {
          isPasswordFocused = true
        } else {
          isEmailFocused = false
          isPasswordFocused = false
          login()
        }
      }
    }
  }
}

One Enum for Many Fields

One boolean per field does not scale. @FocusState accepts any Hashable value, so model the focusable fields as an enum and bind each field with .focused($focus, equals: .case). The property must be optional, because sometimes nothing has focus; focus = nil dismisses the keyboard.

enum FocusableField: Hashable {
  case email
  case password
}

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

  var body: some View {
    Form {
      TextField("email", text: $email)
        .focused($focus, equals: .email)
      SecureField("password", text: $password)
        .focused($focus, equals: .password)
      Button("login", action: login)
    }
    .toolbar {
      ToolbarItem(placement: .keyboard) {
        Button("next") {
          if email.isEmpty { focus = .email }
          else if password.isEmpty { focus = .password }
          else { focus = nil }
        }
      }
    }
    .defaultFocus($focus, .email)
  }
}

defaultFocus sets the focused field when the view appears. A ToolbarItem(placement: .keyboard) puts the next button above the keyboard.

Focusing the Search Field

searchFocused binds focus for the search bar a searchable modifier creates, with the same boolean and Hashable forms. It only targets the search field of the current hierarchy, so one enum can cover the results list and the search bar together.

SearchResultsView(results: store.results)
  .focused($focus, equals: .results)
  .searchable(text: store.$query)
  .searchFocused($focus, equals: .search)

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-mastering-focusstate-property-wrapper" })
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