gh-Dimillian-Skills/swiftui-ui-patterns/references/form.md
Thomas Ricouard 70a15d08db Add SwiftUI UI patterns skill and references
Introduces the 'swiftui-ui-patterns' skill to docs/skills.json, providing best practices and example-driven guidance for building SwiftUI views and components. Adds SKILL.md and a comprehensive set of reference files covering TabView, NavigationStack, sheets, forms, controls, grids, overlays, haptics, focus handling, media, matched transitions, split views, and more.
2026-01-04 18:26:56 +01:00

3 KiB
Raw Permalink Blame History

Form

Intent

Use Form for structured settings, grouped inputs, and action rows. This pattern keeps layout, spacing, and accessibility consistent for data entry screens.

Core patterns

  • Wrap the form in a NavigationStack only when it is presented in a sheet or standalone view without an existing navigation context.
  • Group related controls into Section blocks.
  • Use .scrollContentBackground(.hidden) plus a custom background color when you need design-system colors.
  • Apply .formStyle(.grouped) for grouped styling when appropriate.
  • Use @FocusState to manage keyboard focus in input-heavy forms.

Example: settings-style form

@MainActor
struct SettingsView: View {
  @Environment(Theme.self) private var theme

  var body: some View {
    NavigationStack {
      Form {
        Section("General") {
          NavigationLink("Display") { DisplaySettingsView() }
          NavigationLink("Haptics") { HapticsSettingsView() }
        }

        Section("Account") {
          Button("Edit profile") { /* open sheet */ }
            .buttonStyle(.plain)
        }
        .listRowBackground(theme.primaryBackgroundColor)
      }
      .navigationTitle("Settings")
      .navigationBarTitleDisplayMode(.inline)
      .scrollContentBackground(.hidden)
      .background(theme.secondaryBackgroundColor)
    }
  }
}

Example: modal form with validation

@MainActor
struct AddRemoteServerView: View {
  @Environment(\.dismiss) private var dismiss
  @Environment(Theme.self) private var theme

  @State private var server: String = ""
  @State private var isValid = false
  @FocusState private var isServerFieldFocused: Bool

  var body: some View {
    NavigationStack {
      Form {
        TextField("Server URL", text: $server)
          .keyboardType(.URL)
          .textInputAutocapitalization(.never)
          .autocorrectionDisabled()
          .focused($isServerFieldFocused)
          .listRowBackground(theme.primaryBackgroundColor)

        Button("Add") {
          guard isValid else { return }
          dismiss()
        }
        .disabled(!isValid)
        .listRowBackground(theme.primaryBackgroundColor)
      }
      .formStyle(.grouped)
      .navigationTitle("Add Server")
      .navigationBarTitleDisplayMode(.inline)
      .scrollContentBackground(.hidden)
      .background(theme.secondaryBackgroundColor)
      .scrollDismissesKeyboard(.immediately)
      .toolbar { CancelToolbarItem() }
      .onAppear { isServerFieldFocused = true }
    }
  }
}

Design choices to keep

  • Prefer Form over custom stacks for settings and input screens.
  • Keep rows tappable by using .contentShape(Rectangle()) and .buttonStyle(.plain) on row buttons.
  • Use list row backgrounds to keep section styling consistent with your theme.

Pitfalls

  • Avoid heavy custom layouts inside a Form; it can lead to spacing issues.
  • If you need highly custom layouts, prefer ScrollView + VStack.
  • Dont mix multiple background strategies; pick either default Form styling or custom colors.