How to implement a toggle switch in SwiftUI

iOS 17+ Xcode 16+ Beginner APIs: Toggle Updated: May 11, 2026
TL;DR

Use SwiftUI's built-in Toggle view with a @State bool binding — it renders the native iOS switch control and wires up state automatically. Apply .toggleStyle() to customize the appearance.

import SwiftUI

struct ContentView: View {
    @State private var isEnabled = false

    var body: some View {
        Toggle("Enable notifications", isOn: $isEnabled)
            .padding()
    }
}

Full implementation

The example below shows a settings-style form with multiple toggles, each backed by its own @State variable. Each Toggle is placed inside a Form with grouped sections, which gives you the standard iOS Settings look for free. A computed property drives a dependent UI element to demonstrate real-world conditional logic based on toggle state.

import SwiftUI

struct SettingsView: View {
    @State private var notificationsEnabled = true
    @State private var soundEnabled = true
    @State private var darkModeEnabled = false
    @State private var analyticsEnabled = false

    // Derived: sound only matters when notifications are on
    private var isSoundToggleDisabled: Bool {
        !notificationsEnabled
    }

    var body: some View {
        NavigationStack {
            Form {
                Section("Notifications") {
                    Toggle(isOn: $notificationsEnabled) {
                        Label("Enable notifications", systemImage: "bell.fill")
                    }

                    Toggle(isOn: $soundEnabled) {
                        Label("Sound", systemImage: "speaker.wave.2.fill")
                    }
                    .disabled(isSoundToggleDisabled)
                    .foregroundStyle(isSoundToggleDisabled ? .secondary : .primary)
                }

                Section("Appearance") {
                    Toggle(isOn: $darkModeEnabled) {
                        Label("Dark mode", systemImage: "moon.fill")
                    }
                    .tint(.indigo)
                }

                Section("Privacy") {
                    Toggle(isOn: $analyticsEnabled) {
                        Label("Share analytics", systemImage: "chart.bar.fill")
                    }
                    .tint(.green)

                    if analyticsEnabled {
                        Text("Anonymous usage data helps improve the app.")
                            .font(.footnote)
                            .foregroundStyle(.secondary)
                    }
                }
            }
            .navigationTitle("Settings")
            .navigationBarTitleDisplayMode(.large)
        }
    }
}

#Preview {
    SettingsView()
}

How it works

  1. @State bool binding — Each @State private var owns the source of truth for one toggle. Prefixing with $ passes a two-way Binding<Bool> to Toggle(isOn:), so the view re-renders automatically when the user flips the switch.
  2. Label view as the toggle's content — Using Label("text", systemImage:) inside the trailing closure gives you an SF Symbol icon alongside the title, which VoiceOver reads as a single combined label automatically.
  3. .disabled() for dependent state — The isSoundToggleDisabled computed property derives its value from notificationsEnabled. When the parent toggle is off, .disabled(true) greys out the child toggle and prevents interaction — no manual gesture blocking needed.
  4. .tint() for custom switch colors — Applying .tint(.indigo) or .tint(.green) changes the "on" track color of the switch. This works on iOS 15+ and is the recommended replacement for the deprecated accentColor.
  5. Conditional child content — The if analyticsEnabled { ... } block is a zero-animation way to show or hide explanatory text based on toggle state, keeping the form readable without extra view complexity.

Variants

Button-style toggle (checkbox look)

import SwiftUI

struct CheckboxToggleView: View {
    @State private var isAccepted = false

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            // Built-in button style — renders as a tappable button, not a switch
            Toggle(isOn: $isAccepted) {
                Text("I accept the terms and conditions")
                    .font(.body)
            }
            .toggleStyle(.button)
            .tint(isAccepted ? .green : .gray)

            // Custom checkbox using a completely bespoke ToggleStyle
            Toggle(isOn: $isAccepted) {
                Text("Use custom checkbox style")
            }
            .toggleStyle(CheckboxToggleStyle())
        }
        .padding()
    }
}

struct CheckboxToggleStyle: ToggleStyle {
    func makeBody(configuration: Configuration) -> some View {
        Button {
            configuration.isOn.toggle()
        } label: {
            HStack {
                Image(systemName: configuration.isOn
                    ? "checkmark.square.fill"
                    : "square")
                    .foregroundStyle(configuration.isOn ? .blue : .secondary)
                configuration.label
            }
        }
        .buttonStyle(.plain)
    }
}

#Preview {
    CheckboxToggleView()
}

Observed object / @AppStorage toggle

For toggles that must persist across app launches, swap @State for @AppStorage:

struct PersistentToggleView: View {
    // Reads and writes UserDefaults automatically
    @AppStorage("notificationsEnabled") private var notificationsEnabled = true

    var body: some View {
        Toggle("Enable notifications", isOn: $notificationsEnabled)
            .padding()
    }
}

Common pitfalls

Prompt this with Claude Code

When using Soarias or Claude Code directly to implement this:

Implement a toggle switch in SwiftUI for iOS 17+.
Use Toggle, @State (or @AppStorage for persistence), and .toggleStyle().
Make it accessible (VoiceOver labels, Label views with systemImage).
Add a #Preview with realistic sample data showing multiple toggles in a Form.

Drop this prompt into the Soarias Build phase after your screens are mocked up — Claude Code will scaffold the toggle logic, wire bindings, and add accessibility annotations in one pass.

Related

FAQ

Does this work on iOS 16?

The core Toggle view and .toggleStyle(.button) work from iOS 15+. However, the two-parameter .onChange(of:initial:) closure and some Label rendering refinements are iOS 17+. If you target iOS 16, replace the new onChange with the deprecated single-parameter form and add @available guards where needed.

How do I change the toggle's "on" track color globally?

Apply .tint(Color) on the Toggle itself for per-control tinting, or on a parent container (like the whole Form) to change all toggles at once. Avoid the older .accentColor() modifier — it was deprecated in iOS 15 and has no effect on toggles in iOS 17+.

What's the UIKit equivalent?

In UIKit, UISwitch is the direct counterpart. You set its state via isOn and listen for changes using a .valueChanged target-action. SwiftUI's Toggle removes the boilerplate entirely — the binding replaces both the property write and the action handler.

Last reviewed: 2026-05-11 by the Soarias team.