# Forms

Form guidance for the `swiftui-layout-components` skill.

## When to use Form

Settings, grouped inputs and action rows belong in a `Form`. It gives
consistent layout, spacing and accessibility without hand tuning.

- Add a `NavigationStack` around it only when it is presented in a sheet or
  stands alone with no navigation context.
- For design-system colors, hide the default background with
  `.scrollContentBackground(.hidden)` and supply your own.
- `.formStyle(.grouped)` where the grouped look fits.
- `@FocusState` for moving between fields.

## Settings form

```swift
struct NotificationSettings: View {
    @AppStorage("alerts.digest") private var dailyDigest = true
    @AppStorage("alerts.sound") private var playSound = false

    var body: some View {
        Form {
            Section("Alerts") {
                Toggle("Daily Digest", isOn: $dailyDigest)
                Toggle("Play Sound", isOn: $playSound)
            }
            .listRowBackground(Color.cardSurface)

            Section {
                Button { openSystemSettings() } label: {
                    Label("System Notification Settings", systemImage: "gear")
                }
                .buttonStyle(.plain)
            }
            .listRowBackground(Color.cardSurface)
        }
        .navigationTitle("Notifications")
        .navigationBarTitleDisplayMode(.inline)
        .scrollContentBackground(.hidden)
        .background(Color.screenBackground)
    }

    private func openSystemSettings() {}
}
```

`Color.cardSurface` and `Color.screenBackground` stand for your design-system
colors.

## Modal form with validation

```swift
struct AddFeedSheet: View {
    @Environment(\.dismiss) private var dismiss
    @State private var address = ""
    @FocusState private var addressFocused: Bool

    private var isValid: Bool {
        guard let url = URL(string: address) else { return false }
        return url.scheme == "https" && url.host() != nil
    }

    var body: some View {
        NavigationStack {
            Form {
                Section("Feed Address") {
                    TextField("https://", text: $address)
                        .keyboardType(.URL)
                        .textInputAutocapitalization(.never)
                        .autocorrectionDisabled()
                        .focused($addressFocused)
                }
                Section {
                    Button("Add Feed") {
                        guard isValid else { return }
                        subscribe()
                        dismiss()
                    }
                    .disabled(!isValid)
                }
            }
            .scrollDismissesKeyboard(.immediately)
            .navigationTitle("New Feed")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") { dismiss() }
                }
            }
            .onAppear { addressFocused = true }
        }
    }

    private func subscribe() {}
}
```

The submit button is disabled until the input is valid and checks again when
tapped; the field is focused as soon as the sheet appears.

## Design choices

- A `Form` beats a custom stack for settings and data entry.
- Rows with buttons stay fully tappable with `.contentShape(Rectangle())` and
  `.buttonStyle(.plain)`.
- `.listRowBackground(_:)` matches rows to the theme.

## Pitfalls

- Heavy custom layouts inside a `Form` fight its spacing; for very custom
  screens use `ScrollView` with a `VStack`.
- Choose one background approach: the default form styling or your own
  colors, not a mix of both.
