SwiftUI TextField: Focus, Validation & SecureField
Build real forms in SwiftUI — bindings, keyboard types, @FocusState field navigation, live validation, secure entry, and multiline text that grows with the content.
A SwiftUI TextField binds a String to editable text: TextField("Email", text: $email). Configure the keyboard with .keyboardType, control focus with a @FocusState property and the .focused modifier, and set the return key with .submitLabel. For passwords use SecureField, and for growing multiline input use TextField("Notes", text: $notes, axis: .vertical) available since iOS 16.
Bindings and keyboard configuration
A TextField edits a value through a Binding, most often a String. Choose the right keyboard for the data with .keyboardType — .emailAddress for emails, .numberPad for PINs — and pair it with .textInputAutocapitalization and .autocorrectionDisabled so the field does not fight the user. For anything other than free text, disabling autocorrect prevents frustrating substitutions. These modifiers are cheap and dramatically improve the typing experience, so set them deliberately on every field.
@State private var email = ""
TextField("Email address", text: $email)
.keyboardType(.emailAddress)
.textContentType(.emailAddress)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.textFieldStyle(.roundedBorder)Managing focus with @FocusState
@FocusState lets you read and control which field is active. Declare a state property — often an enum of your fields — and attach .focused(_:equals:) to each TextField. You can then move focus programmatically, for example advancing to the next field when the user taps return, or dismissing the keyboard by setting focus to nil. This is the correct, first-party way to chain fields; avoid UIKit responder hacks now that @FocusState exists.
enum Field { case email, password }
struct LoginForm: View {
@State private var email = ""
@State private var password = ""
@FocusState private var focus: Field?
var body: some View {
VStack {
TextField("Email", text: $email)
.focused($focus, equals: .email)
.submitLabel(.next)
SecureField("Password", text: $password)
.focused($focus, equals: .password)
.submitLabel(.go)
}
.onSubmit {
focus = focus == .email ? .password : nil
}
}
}Live validation
Validate as the user types by deriving a computed property from the bound value and reacting to it. A common pattern is to show an inline error only after the field has been touched, so you don't scold the user before they have finished typing. Drive the submit button's .disabled state from the same computed validity. Keep validation logic in the view model when it grows, but for a single field a computed Bool in the view is perfectly clean.
@State private var email = ""
private var isValidEmail: Bool {
email.contains("@") && email.contains(".")
}
var body: some View {
VStack(alignment: .leading, spacing: 4) {
TextField("Email", text: $email)
.textFieldStyle(.roundedBorder)
if !email.isEmpty && !isValidEmail {
Text("Enter a valid email address")
.font(.caption)
.foregroundStyle(.red)
}
}
}SecureField for passwords
SecureField mirrors TextField but masks the input, and it is what you should use for passwords and other secrets. Set .textContentType(.password) or .newPassword so the system offers Passwords app autofill and strong-password suggestions. A frequent request is a reveal toggle; implement it by conditionally swapping between SecureField and TextField backed by the same binding, so the entered text is preserved when the user flips visibility.
@State private var password = ""
@State private var isRevealed = false
HStack {
Group {
if isRevealed {
TextField("Password", text: $password)
} else {
SecureField("Password", text: $password)
}
}
.textContentType(.password)
Button {
isRevealed.toggle()
} label: {
Image(systemName: isRevealed ? "eye.slash" : "eye")
}
}Multiline text and custom styles
Since iOS 16, TextField grows vertically when you pass axis: .vertical, replacing most uses of the older TextEditor for short-to-medium input. Constrain its growth with .lineLimit(_:reservesSpace:) so it starts at a sensible height and expands to a cap. For styling, .textFieldStyle(.roundedBorder) covers most needs, but you can build a custom look by conforming to TextFieldStyle. On iOS 26, wrapping fields in a .glassEffect() container gives inputs the Liquid Glass treatment consistent with the rest of the system.
@State private var notes = ""
TextField("Notes", text: $notes, axis: .vertical)
.lineLimit(3...8) // starts at 3 lines, grows to 8
.textFieldStyle(.roundedBorder)
.submitLabel(.return)Auth forms, already validated
The Swift Kit's Supabase sign-in and sign-up screens ship with validated email/password fields, focus chaining, and autofill wired up — no form boilerplate.
How to dismiss the keyboard in SwiftUI
Give users a reliable way to close the keyboard from a TextField.
- 1
Declare focus state
Add a @FocusState property to track the active field.
@FocusState private var isFocused: Bool - 2
Bind the field
Attach .focused to the TextField so SwiftUI can track and change focus.
TextField("Search", text: $query).focused($isFocused) - 3
Add a Done button
Put a toolbar button above the keyboard that sets focus to false.
.toolbar { ToolbarItemGroup(placement: .keyboard) { Spacer(); Button("Done") { isFocused = false } } }
Frequently Asked Questions
How do I move focus to the next TextField in SwiftUI?
How do I make a multiline TextField in SwiftUI?
How do I add a show/hide password toggle to SecureField in SwiftUI?
How do I validate a TextField in SwiftUI as the user types?
How do I set the return key label on a SwiftUI TextField?
Keep exploring
Ship the boring parts on day one
The Swift Kit is a $99 one-time SwiftUI boilerplate with auth forms, validation, paywalls, and AI ready to go — lifetime updates and a 14-day refund.
Get The Swift Kit — $99One-time purchase · Lifetime updates · 14-day refund