# How to Manage Focus State and Field Chaining in SwiftUI Forms

> Master SwiftUI forms with @FocusState and enum identifiers. Control keyboard focus, chain fields with onSubmit, and handle dynamic lists effectively. Improve user experience today.

- Repository: [Thomas Ricouard/Skills](https://github.com/Dimillian/Skills)
- Tags: how-to-guide
- Published: 2026-04-01

---

**Use the `@FocusState` property wrapper with enum identifiers to programmatically control keyboard focus, automatically chain fields via `.onSubmit`, and handle dynamic lists using associated values.**

SwiftUI's `@FocusState` property wrapper provides a declarative mechanism to control which input field holds the keyboard cursor without leaking imperative logic into view models. The Dimillian/Skills repository demonstrates production-ready patterns for managing focus state and field chaining across static and dynamic form layouts. This guide breaks down the three architectural approaches documented in [`swiftui-ui-patterns/references/focus.md`](https://github.com/Dimillian/Skills/blob/main/swiftui-ui-patterns/references/focus.md), from simple boolean toggles to hashable enums with associated values.

## Core Architecture of Focus Management

Every focus implementation in the Skills codebase follows a consistent three-step pattern. First, define a focus identifier—either a `Bool` for single fields or a `Hashable` enum for multiple inputs. Second, bind the UI by attaching the `.focused(_:equals:)` modifier (or `.focused(_:)` for boolean cases) to each `TextField` or `SecureField`. Third, programmatically drive focus changes by setting the bound value in response to lifecycle events like `onAppear`, user actions like `onSubmit`, or after mutations to a dynamic list.

## Pattern 1: Single-Field Focus

For views containing only one primary input, a boolean `@FocusState` provides the simplest implementation. This pattern isolates focus logic entirely within the view’s local state.

```swift
struct AddServerView: View {
    @State private var server = ""
    @FocusState private var isServerFieldFocused: Bool

    var body: some View {
        Form {
            TextField("Server", text: $server)
                .focused($isServerFieldFocused)
        }
        .onAppear { isServerFieldFocused = true }
    }
}

```

In this example from the repository’s reference documentation, setting `isServerFieldFocused` to `true` inside `.onAppear` immediately presents the keyboard when the view appears, providing a seamless onboarding experience.

## Pattern 2: Enum-Based Field Chaining

When managing multiple static fields, define a `Hashable` enum where each case represents a focusable field. This enables type-safe field chaining using the `.onSubmit` modifier, which triggers when the user taps the keyboard’s return key.

```swift
struct EditTagView: View {
    @State private var title = ""
    @State private var symbol = ""
    @State private var newTag = ""

    enum FocusField { case title, symbol, newTag }
    @FocusState private var focusedField: FocusField?

    var body: some View {
        Form {
            TextField("Title", text: $title)
                .focused($focusedField, equals: .title)
                .onSubmit { focusedField = .symbol }

            TextField("Symbol", text: $symbol)
                .focused($focusedField, equals: .symbol)
                .onSubmit { focusedField = .newTag }

            TextField("New Tag", text: $newTag)
                .focused($focusedField, equals: .newTag)
        }
        .onAppear { focusedField = .title }
    }
}

```

Setting `focusedField` to the next enum case inside `.onSubmit` creates a linear navigation flow that respects the keyboard’s return key and works across iOS and macOS.

## Pattern 3: Dynamic Focus for Variable Lists

For forms where the number of inputs changes at runtime—such as a poll creator or dynamic list builder—use an enum with an associated value (e.g., `.option(Int)`). After appending a new row, introduce a minimal async delay to allow the view hierarchy to settle before shifting focus to the new field.

```swift
struct PollView: View {
    enum FocusField: Hashable { case option(Int) }
    @FocusState private var focused: FocusField?
    @State private var options: [String] = ["", ""]
    @State private var currentIndex = 0

    var body: some View {
        VStack {
            ForEach(options.indices, id: \.self) { index in
                TextField("Option \(index + 1)", text: $options[index])
                    .focused($focused, equals: .option(index))
                    .onSubmit { addOption(at: index) }
            }
        }
        .onAppear { focused = .option(0) }
    }

    private func addOption(at index: Int) {
        options.append("")
        currentIndex = index + 1
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
            focused = .option(currentIndex)
        }
    }
}

```

The `DispatchQueue.main.asyncAfter` delay prevents "focus-lost" bugs that occur when attempting to focus a view that has not yet finished its layout pass.

## Best Practices for Production Apps

**Keep focus state local.** Avoid elevating `@FocusState` into shared view models or global state objects. Localizing focus to the view that owns the inputs prevents unintended side-effects across navigation stacks.

**Leverage `.onSubmit` for navigation.** This modifier respects system keyboard behaviors—including hardware keyboard support and the `SubmitLabel` configuration—better than custom gesture recognizers.

**Delay focus changes after mutations.** When inserting new rows or modifying the view hierarchy, always defer focus assignment by at least one run loop cycle (0.01 seconds) to ensure the target view exists in the hierarchy.

**Combine with scroll dismissal.** Attach `.scrollDismissesKeyboard(.interactively)` to surrounding `ScrollView` or `Form` containers to allow users to dismiss the keyboard by scrolling, creating a polished native experience.

## Summary

- **Use `@FocusState`** to declare focus identifiers as either `Bool` values for single fields or `Hashable` enums for multiple inputs.
- **Bind inputs** with `.focused(_:equals:)` to synchronize the keyboard cursor with your state.
- **Chain fields** by updating the focus value inside `.onSubmit` handlers to move the cursor linearly through forms.
- **Handle dynamic lists** with associated-value enums (`.option(Int)`) and brief async delays after mutations to avoid focus conflicts.
- **Reference implementation** for all patterns is documented in [`swiftui-ui-patterns/references/focus.md`](https://github.com/Dimillian/Skills/blob/main/swiftui-ui-patterns/references/focus.md) within the Dimillian/Skills repository.

## Frequently Asked Questions

### How do I move focus to the next field when the user presses return?

Attach the `.onSubmit` modifier to your `TextField` and update the `@FocusState` bound value to the next field’s identifier inside the closure. For enum-based focus, this looks like `.onSubmit { focusedField = .nextCase }`.

### Why do I need a delay when setting focus on dynamically added fields?

SwiftUI’s view hierarchy requires a layout pass to instantiate new views after state mutations. Attempting to focus a field immediately after appending to an array often targets a view that does not yet exist, causing the focus change to fail. A minimal `DispatchQueue.main.asyncAfter` delay of 0.01 seconds allows the hierarchy to settle before the focus assignment executes.

### Should I use a Bool or an enum for FocusState?

Use a **Bool** only when the view contains a single focusable field. Use an **enum** for any scenario with multiple fields, as it provides type-safe identifiers and scales naturally to chained navigation and dynamic lists via associated values.

### How do I dismiss the keyboard when scrolling in a SwiftUI form?

Apply the `.scrollDismissesKeyboard(.interactively)` or `.scrollDismissesKeyboard(.immediately)` modifier to the parent `ScrollView`, `List`, or `Form` container. This interacts directly with the `@FocusState` binding to nil out the focus value when the user scrolls, automatically dismissing the keyboard according to the specified behavior.