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?

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?

  • Form groups input controls into a structured, Settings-style layout
  • TextField and SecureField collect text input via bindings
  • Toggle represents boolean choices with an on/off switch
  • Picker and DatePicker let users select from predefined options
  • Slider and Stepper handle numeric input across ranges
  • .onChange lets 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.