@FocusState is a property wrapper in SwiftUI, introduced in iOS 15, that allows you to programmatically control input focus on text fields and other elements. Before its introduction, developers had to use UIViewRepresentable to access UIKit methods becomeFirstResponder and resignFirstResponder. @FocusState solves this problem natively: you bind a property to a field via the .focused() modifier, after which setting or clearing focus is done with a simple value assignment. According to Apple Developer Documentation — FocusState (2025), @FocusState supports two modes: Bool for simple management (focus is on or off) and enum for multiple fields, where each case corresponds to a specific input field.
Key Takeaways
@FocusState is a property wrapper that ties the focus state to a specific input field or other focusable element in SwiftUI. Unlike UIKit, where focus management happens through becomeFirstResponder and resignFirstResponder methods, SwiftUI uses a declarative approach: you declare a state (@FocusState) and bind it to an element via the .focused() modifier. Changing the state automatically changes the focus.
Before @FocusState was introduced in iOS 15, developers had to create UIViewRepresentable wrappers around UITextField or use third-party libraries. @FocusState is integrated directly into SwiftUI and works with TextField, TextEditor, SecureField, and SearchField. This makes code cleaner, reduces the number of UIKit bridges, and improves testability.
According to WWDC Session 10136 — What's new in SwiftUI (2024), @FocusState uses SwiftUI's preference key system to pass focus information between elements. When a field receives focus, SwiftUI automatically updates the associated @FocusState property, allowing you to react to focus changes in code.
The simplest way to use @FocusState is with the Bool type. When a field is focused, the property is true. When focus is lost — false. You can force focus by setting it to true, or clear it by setting it to false.
struct LoginForm: View {
@State var email = ""
@FocusState var isEmailFocused: Bool
var body: some View {
VStack {
TextField("Email", text: $email)
.focused($isEmailFocused)
Button("Show Keyboard") {
isEmailFocused = true
}
Button("Hide Keyboard") {
isEmailFocused = false
}
}
}
}
In this example, isEmailFocused automatically becomes true when the user taps on the text field, and false when the keyboard is dismissed. The buttons allow you to manage focus programmatically — useful for custom keyboards, "Next" buttons, and situations where you need to force-dismiss the keyboard after form submission.
For forms with multiple fields, @FocusState supports an enum conforming to the FocusStateValue (or Hashable) protocol. Each enum case corresponds to a specific field. This allows switching focus between fields — for example, when the user presses "Next" on the keyboard to move to the next field.
struct RegistrationForm: View {
enum Field: Hashable {
case email
case password
case confirmPassword
}
@State var email = ""
@State var password = ""
@State var confirmPassword = ""
@FocusState var focusedField: Field?
var body: some View {
Form {
TextField("Email", text: $email)
.focused($focusedField, equals: .email)
.onSubmit { focusedField = .password }
SecureField("Password", text: $password)
.focused($focusedField, equals: .password)
.onSubmit { focusedField = .confirmPassword }
SecureField("Confirm", text: $confirmPassword)
.focused($focusedField, equals: .confirmPassword)
.onSubmit { submitForm() }
}
}
}
Note the .onSubmit modifier — it is called when the user presses "Return" on the keyboard. Inside .onSubmit we switch focusedField to the next field, which automatically moves focus. The last field calls submitForm() to submit the form.
@FocusState provides a simple way to hide the keyboard — just set the property to nil (for enum) or false (for Bool). However, sometimes you need to hide the keyboard without tying it to a specific field — for example, when tapping on empty space. In this case, there are several approaches.
struct DismissKeyboardView: View {
@State var text = ""
@FocusState var isFocused: Bool
var body: some View {
TextField("Enter text", text: $text)
.focused($isFocused)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
isFocused = false
}
}
}
}
}
The .toolbar modifier with placement .keyboard adds a button above the keyboard. This is a standard UX pattern in iOS for dismissing the keyboard. An alternative approach is to use .onTapGesture on the root VStack to reset focus when tapping on the background.
@FocusState combines perfectly with form validation. A typical pattern: after pressing the "Submit" button, validate all fields and set focus to the first field with an error. This improves the user experience — the user immediately sees which field needs to be corrected.
struct ValidatedForm: View {
enum Field: Hashable { case name; case phone }
@State var name = ""
@State var phone = ""
@FocusState var focusedField: Field?
@State var errors: [String] = []
var body: some View {
Form {
TextField("Name", text: $name)
.focused($focusedField, equals: .name)
TextField("Phone", text: $phone)
.focused($focusedField, equals: .phone)
Button("Submit") { validateAndSubmit() }
}
}
func validateAndSubmit() {
if name.isEmpty {
focusedField = .name
return
}
if phone.isEmpty {
focusedField = .phone
return
}
// submit form
}
}
In this example, if the name field is empty, focus moves to it, and the user immediately sees where the error is. If name is filled, phone is checked. This is natural behavior for forms — the user fills fields from top to bottom, and validation follows the same order.
The most common mistake is trying to use @FocusState with a type that does not conform to Hashable. @FocusState requires the property type to be Hashable (Bool and optional enums already conform). If you are trying to use a custom structure, make sure it implements Hashable.
// ❌ Wrong: two @FocusState Bool instead of enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool
// ✅ Correct: single enum @FocusState
enum Field: Hashable { case name; case email }
@FocusState var focusedField: Field?
Frequently Asked Questions
@FocusState is available from iOS 15, iPadOS 15, macOS 12, tvOS 15, and watchOS 8. For projects supporting iOS 14 and below, use UIViewRepresentable with UITextField and becomeFirstResponder, or third-party libraries with custom focus management implementation.
Yes, you need to implement FocusState support in the custom UIViewRepresentable through the UIViewRepresentable protocol. The custom view must have becomeFirstResponder and resignFirstResponder. SwiftUI will automatically link @FocusState with these methods if you specify the .focused() modifier.
In List or Form, cells can be reused, which breaks the @FocusState binding with the field. Solution: add the .id() modifier with a unique identifier for each TextField. For example: .id(fieldName). This forces SwiftUI to create a separate View instance for each field.
Add .onTapGesture to the root container (VStack, ZStack) and reset focus: focusedField = nil. However, .onTapGesture may block taps on buttons inside — use a container with .contentShape(Rectangle()) and .onTapGesture on it, or a custom UIKitBackgroundView.
@FocusState does not provide a direct API for keyboard animation — this is iOS system behavior. However, you can react to focus changes with .onChange(of: focusedField) or .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) for custom content animation.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also