SwiftUI Toggle: Bindings, Styles, and a Custom ToggleStyle
A hands-on Toggle guide — bind it to state, swap between switch and button styles, build your own ToggleStyle, tint it to your brand, drop it inside Form and List, and gate premium features cleanly. Real Swift 6 code and pitfalls.
A SwiftUI Toggle needs a Bool binding and a label: Toggle("Notifications", isOn: $enabled). Change its look with .toggleStyle(.switch), .toggleStyle(.button), or a custom type conforming to ToggleStyle. Color the switch with .tint(). Inside a Form or List each Toggle becomes a settings row automatically. To gate a premium feature, disable or intercept the toggle unless the user holds the required entitlement.
The binding is the whole point
A Toggle is a two-way control, so it always takes a Binding<Bool> plus a label. The simplest source is an @State property you pass with the $ prefix. Because it's a binding, the Toggle both reads the current value and writes the new one when the user flips it — you never manually respond to a tap. If you need to react to changes (persist a setting, fire analytics), attach .onChange(of:) to the binding's value rather than trying to intercept the toggle itself. For settings that must survive relaunches, back the state with @AppStorage so the value persists to UserDefaults automatically.
struct NotificationSetting: View {
@AppStorage("pushEnabled") private var pushEnabled = true
var body: some View {
Toggle("Push Notifications", isOn: $pushEnabled)
.onChange(of: pushEnabled) { _, newValue in
NotificationManager.shared.setEnabled(newValue)
}
}
}Built-in styles and tint
Toggle ships two built-in styles. The default on iOS is .switch, the familiar sliding capsule. .button turns the toggle into a bordered button that shows a selected state — handy in toolbars or filter bars where a switch would look out of place. Apply either with .toggleStyle. To recolor the switch's on state, use .tint(); the older .toggleStyle(SwitchToggleStyle(tint:)) initializer is deprecated, so prefer the standalone .tint modifier. Tint respects your accent color by default, which is why a single accent applied at the app root keeps every switch on-brand without per-toggle overrides.
- .toggleStyle(.switch) — default sliding switch
- .toggleStyle(.button) — bordered selectable button, great in toolbars
- .tint(.green) — recolors the on state; replaces deprecated SwitchToggleStyle(tint:)
- A label with an SF Symbol works well with .button style
struct StyleShowcase: View {
@State private var wifi = true
@State private var starred = false
var body: some View {
VStack(spacing: 24) {
Toggle("Wi-Fi", isOn: $wifi)
.toggleStyle(.switch)
.tint(.green)
Toggle(isOn: $starred) {
Label("Star", systemImage: "star.fill")
}
.toggleStyle(.button)
}
.padding()
}
}Building a custom ToggleStyle
When neither built-in style fits — say you want a pill with a checkmark, or a card that highlights when on — conform a struct to ToggleStyle. Its makeBody(configuration:) receives a Configuration whose isOn Bool tells you the state and whose label carries your original label view. Crucially, you must toggle configuration.isOn.wrappedValue yourself in the tap handler, because your custom body replaces the default interaction. Wrap the flip in withAnimation for a smooth transition. Once written, apply it with .toggleStyle(MyStyle()) anywhere, and it composes with tint and other modifiers just like the built-ins.
struct CheckPillToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.label
Spacer()
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
.foregroundStyle(configuration.isOn ? .accentColor : .secondary)
}
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.snappy) { configuration.isOn.toggle() }
}
}
}
// Usage:
// Toggle("Sync over cellular", isOn: $cellular)
// .toggleStyle(CheckPillToggleStyle())Toggles inside Form and List
Drop a Toggle into a Form or List and SwiftUI renders it as a native settings row — label on the left, control trailing-aligned, with correct insets and separators. This is how you build a Settings screen with almost no layout code. Group related switches under a Section with a header and footer to explain what each does. A subtle pitfall: if a toggle should enable or disable dependent rows, don't hide them abruptly — use .disabled(!parentToggle) so the rows stay visible but dimmed, which is less jarring and keeps the layout stable as the user flips the parent.
struct SettingsForm: View {
@AppStorage("notifications") private var notifications = true
@AppStorage("sound") private var sound = true
var body: some View {
Form {
Section("Alerts") {
Toggle("Notifications", isOn: $notifications)
Toggle("Play Sound", isOn: $sound)
.disabled(!notifications)
}
}
}
}Ship your SwiftUI app in 5 emails
A free 5-part course on the parts that actually stall launches: paywall, auth, onboarding, App Store review, and pricing. No fluff, unsubscribe anytime.
A settings screen you don't have to wire up
The Swift Kit's settings module ships toggles backed by @AppStorage and pro switches gated behind a RevenueCat entitlement — persistence and paywall interception already handled.
Rather have it done for you? I set up and ship your app from $499. See how
How to build a custom ToggleStyle
A custom style lets you replace the switch entirely while keeping Toggle's binding semantics.
- 1
Conform to ToggleStyle
Create a struct that implements makeBody(configuration:).
struct CardToggleStyle: ToggleStyle { func makeBody(configuration: Configuration) -> some View { /* ... */ } } - 2
Read the state
configuration.isOn is a Binding<Bool>; configuration.label is your original label.
let on = configuration.isOn.wrappedValue - 3
Flip it on tap
Your body must mutate configuration.isOn itself, ideally inside withAnimation.
.onTapGesture { withAnimation { configuration.isOn.toggle() } } - 4
Apply the style
Attach it to any Toggle; it composes with tint and other modifiers.
Toggle("Dark Mode", isOn: $dark).toggleStyle(CardToggleStyle())
Frequently Asked Questions
Why does my custom SwiftUI ToggleStyle not respond to taps even though makeBody renders correctly?
How do I change the on-state color of a SwiftUI switch Toggle without using the deprecated SwitchToggleStyle(tint:)?
Can a SwiftUI Toggle drive a value that isn't a simple @State Bool, like an entitlement-gated setting?
Why do my dependent SwiftUI Form rows jump around when a parent Toggle is switched off?
What's the difference between .toggleStyle(.switch) and .toggleStyle(.button) in SwiftUI?
Keep exploring
Ship settings and paywalls the first day
The Swift Kit is a $99 one-time SwiftUI boilerplate with a design system, RevenueCat paywalls, and a settings screen where toggles persist and gate premium features out of the box.
Get The Swift Kit — $99One-time purchase · Lifetime updates · 14-day refund