Forms and User Input in SwiftUI: `TextField`, `Toggle`, `Picker`, and `Slider`
Every great app needs a way to hear from its users — a name, a preference, a choice. Think of Remy from Ratatouille: he needs to know what dish a customer wants, how spicy they like it, and whether they have allergies. SwiftUI gives you a full kitchen of input controls to collect exactly what you need.
You’ll learn how to use TextField, Toggle, Picker, Slider, Stepper, and the Form container to gather user
input. We won’t cover custom styling or advanced validation frameworks — those are topics for future posts.
This post assumes you are comfortable with state management in SwiftUI.
What You’ll Learn
- What Is a Form?
- TextField and SecureField
- Toggle
- Picker and DatePicker
- Slider and Stepper
- Reacting to Input Changes
- Common Mistakes
- What’s Next?
What Is a Form?
A form is a container view that groups input controls into a structured layout. On iOS, a Form automatically
renders its contents in a grouped list style — the same look you see in the Settings app.
Apple Docs:
Form— SwiftUI
Think of a Form like the order slip at Gusteau’s restaurant. Each section of the slip collects a different kind of
information — appetizer, main course, dessert — and the form keeps everything organized.
struct OrderFormView: View {
@State private var customerName = ""
@State private var wantsExtraSpice = false
var body: some View {
Form {
TextField("Customer Name", text: $customerName)
Toggle("Extra Spice", isOn: $wantsExtraSpice)
}
}
}
This creates a Settings-style grouped list with a text field and a toggle. Every control inside a Form needs a
binding (the $ prefix) to a @State property so SwiftUI knows where to store the user’s input. If you need a
refresher on @State and bindings, revisit state management.
TextField and SecureField
A TextField lets the user type free-form text. You provide a placeholder string and a binding to a @State property.
Apple Docs:
TextField— SwiftUI
struct ChefProfileView: View {
@State private var chefName = ""
@State private var secretRecipe = ""
var body: some View {
Form {
TextField("Chef Name", text: $chefName)
SecureField("Secret Recipe Code", text: $secretRecipe)
}
}
}
SecureField works the same way as TextField but masks the input with dots — perfect for passwords or secret recipe
codes that Skinner shouldn’t see.
Tip: Use the
.textContentType()modifier to help iOS auto-fill fields. For example,.textContentType(.name)tells the system this field expects a person’s name.
Toggle
A Toggle is a switch that represents an on/off choice — like deciding whether Remy’s dish should include peanuts.
Apple Docs:
Toggle— SwiftUI
struct DishOptionsView: View {
@State private var isVegetarian = false
@State private var includesDessert = true
var body: some View {
Form {
Toggle("Vegetarian", isOn: $isVegetarian)
Toggle("Include Dessert", isOn: $includesDessert)
}
}
}
Each Toggle takes a label string and a binding to a Bool. When the user flips the switch, SwiftUI updates the
@State property and redraws any views that depend on it.
Picker and DatePicker
A Picker lets users select one option from a list. It’s like choosing your favorite Pixar movie from a menu.
Apple Docs:
Picker— SwiftUI
struct MoviePickerView: View {
@State private var selectedMovie = "Up"
let movies = ["Up", "WALL-E", "Coco", "Ratatouille", "Toy Story"]
var body: some View {
Form {
Picker("Favorite Movie", selection: $selectedMovie) {
ForEach(movies, id: \.self) { movie in
Text(movie)
}
}
}
}
}
Inside a Form, a Picker shows the current selection and pushes a list of options when tapped. The selection
binding stores whichever option the user picks.
DatePicker
DatePicker is a specialized picker for dates. It handles calendars, time zones, and formatting for you.
struct ReservationView: View {
@State private var reservationDate = Date()
var body: some View {
Form {
DatePicker(
"Reservation Date",
selection: $reservationDate,
displayedComponents: .date
)
}
}
}
The displayedComponents parameter controls whether you show .date, .hourAndMinute, or both. The picker style
adapts automatically inside a Form.
Slider and Stepper
A Slider lets users pick a value from a continuous range — like setting the spice level on a dish from 0 to 10.
Apple Docs:
Slider— SwiftUI
struct SpiceLevelView: View {
@State private var spiceLevel = 5.0
var body: some View {
Form {
Slider(value: $spiceLevel, in: 0...10, step: 1) {
Text("Spice Level")
}
Text("Spice: \(Int(spiceLevel))/10")
}
}
}
The user drags the thumb to select a value. The step parameter snaps the value to whole numbers.
A Stepper provides plus and minus buttons for precise numeric input — ideal for choosing how many guests are coming to
Gusteau’s.
struct GuestCountView: View {
@State private var guestCount = 2
var body: some View {
Form {
Stepper("Guests: \(guestCount)", value: $guestCount, in: 1...20)
}
}
}
Stepper takes a binding, a range, and an optional step value. The label updates automatically as the user taps the
buttons.
Reacting to Input Changes
Sometimes you need to run code when a value changes — for example, validating that a customer name isn’t empty. The
.onChange modifier watches a value and runs a closure when it changes.
struct ValidatedNameView: View {
@State private var name = ""
@State private var isValid = false
var body: some View {
Form {
TextField("Customer Name", text: $name)
if !isValid && !name.isEmpty {
Text("Name must be at least 2 characters")
.foregroundStyle(.red)
}
}
.onChange(of: name) { oldValue, newValue in
isValid = newValue.count >= 2
}
}
}
Every time the user types a character, .onChange fires, and the validation message appears or disappears based on the
result. This is the simplest way to add input validation without a separate framework.
Common Mistakes
Forgetting the Dollar Sign on Bindings
Every form control needs a binding — a two-way connection to your state. If you forget the $ prefix, the control
won’t update the state.
// ❌ Don't do this — passes the value, not a binding
TextField("Name", text: name)
// ✅ Do this — passes a binding with $
TextField("Name", text: $name)
Without the $, you’re passing a plain String instead of a Binding<String>, and the compiler will show an error.
Using Picker Without an id
When you create Picker options with ForEach, you must provide an id so SwiftUI can tell each option apart.
// ❌ Don't do this — missing id
ForEach(movies) { movie in
Text(movie)
}
// ✅ Do this — id: \.self for simple strings
ForEach(movies, id: \.self) { movie in
Text(movie)
}
For arrays of String or other simple types, id: \.self uses the value itself as the identifier.
Placing Controls Outside a Form
Form controls work anywhere in SwiftUI, but they look different outside a Form. A Picker inside a Form shows a
navigation-style row. The same Picker in a plain VStack renders as a pop-up menu by default. Neither is wrong, but
the appearance changes — so always preview your layout.
What’s Next?
Formgroups input controls into a structured, Settings-style layoutTextFieldandSecureFieldcollect text input via bindingsTogglerepresents boolean choices with an on/off switchPickerandDatePickerlet users select from predefined optionsSliderandStepperhandle numeric input across ranges.onChangelets you react to input changes for validation
Now that you can collect user input, it’s time to learn how to present new views on top of your current screen. Head over to Presenting Views in SwiftUI to explore sheets, alerts, and confirmation dialogs.