```html SwiftUI: How to Build a Color Picker (iOS 17+, 2026)

How to build a color picker in SwiftUI

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

SwiftUI's built-in ColorPicker view presents Apple's system color wheel in a single line of code — just bind it to a @State var color: Color and you're done. Pass supportsOpacity: false if you only need solid colors.

import SwiftUI

struct TLDRColorPickerView: View {
    @State private var selectedColor: Color = .blue

    var body: some View {
        Form {
            ColorPicker("Accent color", selection: $selectedColor)
            RoundedRectangle(cornerRadius: 12)
                .fill(selectedColor)
                .frame(height: 60)
        }
    }
}

Full implementation

The example below wires ColorPicker into a settings-style Form, persists the chosen color to AppStorage via its hex string representation, and live-previews the result on a swatch. A reset button demonstrates programmatic color mutation, and the supportsOpacity toggle shows how to gate the alpha channel at runtime.

import SwiftUI

// MARK: - Color persistence helpers

extension Color {
    /// Encode to a CSS-style hex string, e.g. "#FF6B00FF"
    var hexString: String {
        let ui = UIColor(self)
        var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
        ui.getRed(&r, green: &g, blue: &b, alpha: &a)
        return String(format: "#%02X%02X%02X%02X",
                      Int(r * 255), Int(g * 255),
                      Int(b * 255), Int(a * 255))
    }

    /// Decode from a hex string produced by hexString above
    init(hexString: String) {
        let hex = hexString.trimmingCharacters(in: .init(charactersIn: "#"))
        guard hex.count == 8,
              let value = UInt64(hex, radix: 16) else {
            self = .blue; return
        }
        self.init(
            red:   Double((value >> 24) & 0xFF) / 255,
            green: Double((value >> 16) & 0xFF) / 255,
            blue:  Double((value >>  8) & 0xFF) / 255,
            opacity: Double(value & 0xFF) / 255
        )
    }
}

// MARK: - Main view

struct ColorPickerSettingsView: View {
    // Persisted as a hex string so it survives app restarts
    @AppStorage("accentColorHex") private var accentColorHex: String = Color.blue.hexString

    @State private var selectedColor: Color = .blue
    @State private var supportsOpacity: Bool = true

    private let defaultColor: Color = .blue

    var body: some View {
        NavigationStack {
            Form {
                // ── Live swatch ──────────────────────────────────────
                Section {
                    RoundedRectangle(cornerRadius: 16)
                        .fill(selectedColor)
                        .frame(maxWidth: .infinity)
                        .frame(height: 100)
                        .overlay(
                            Text("Preview")
                                .font(.headline)
                                .foregroundStyle(.white.opacity(0.85))
                        )
                        .listRowInsets(.init())
                        .accessibilityLabel("Color preview swatch")
                }

                // ── Picker ───────────────────────────────────────────
                Section("Pick a color") {
                    ColorPicker(
                        "Accent color",
                        selection: $selectedColor,
                        supportsOpacity: supportsOpacity
                    )
                    .accessibilityHint("Opens the system color picker sheet")

                    Toggle("Allow transparency", isOn: $supportsOpacity)
                }

                // ── Actions ──────────────────────────────────────────
                Section {
                    Button("Reset to default") {
                        selectedColor = defaultColor
                    }
                    .foregroundStyle(.red)
                }
            }
            .navigationTitle("Color Picker")
            .navigationBarTitleDisplayMode(.inline)
            // Persist whenever the binding changes
            .onChange(of: selectedColor) { _, newColor in
                accentColorHex = newColor.hexString
            }
            // Restore persisted color on appear
            .onAppear {
                selectedColor = Color(hexString: accentColorHex)
            }
        }
    }
}

// MARK: - Preview

#Preview {
    ColorPickerSettingsView()
}

How it works

  1. ColorPicker bindingColorPicker("Accent color", selection: $selectedColor, supportsOpacity: supportsOpacity) is the only API call needed. SwiftUI renders an interactive color swatch that opens Apple's system color wheel sheet when tapped. The $selectedColor two-way binding is updated in real time as the user drags the hue/saturation wheel or types a hex value.
  2. Opacity control — The supportsOpacity parameter (defaulting to true) shows or hides the opacity/alpha slider at the bottom of the picker sheet. The Toggle in the form passes this value through live, so you can see the alpha slider appear and disappear without restarting.
  3. Persistence via AppStorageColor isn't directly AppStorage-compatible, so the hexString helper converts it to a String for storage. The .onChange(of: selectedColor) modifier (using the new two-argument iOS 17 closure) writes on every change, while .onAppear restores it on launch.
  4. Live preview swatch — The RoundedRectangle().fill(selectedColor) redraws whenever the binding changes, giving instant visual feedback without any extra state management.
  5. Reset button — Programmatically assigning selectedColor = defaultColor inside a Button action shows that Color is a plain value type — mutations propagate to the picker swatch immediately.

Variants

Label-free swatch button (inline style)

Use a Label with hidden text when you want only the color swatch to appear — common in toolbars or grids where space is tight.

struct SwatchOnlyPicker: View {
    @State private var color: Color = .orange

    var body: some View {
        HStack {
            Text("Highlight color")
                .font(.subheadline)
            Spacer()
            ColorPicker(
                selection: $color,
                supportsOpacity: false
            ) {
                // Empty label — the swatch IS the control
                EmptyView()
            }
            .labelsHidden()
            .frame(width: 32, height: 32)
            .accessibilityLabel("Highlight color picker")
        }
        .padding()
    }
}

Multiple color pickers in a list

When you need several named color slots (e.g., a theme builder), use a ForEach over an array of @Binding-compatible items. With SwiftData you can store Color as a String attribute using the same hexString helper above, then reconstruct colors in a computed property. Each row in the list gets its own independent ColorPicker with a distinct label so VoiceOver can distinguish them.

Common pitfalls

Prompt this with Claude Code

When using Soarias or Claude Code directly to implement this:

Implement a color picker in SwiftUI for iOS 17+.
Use ColorPicker with a @State Color binding.
Persist the chosen color to AppStorage as a hex string.
Support toggling opacity/alpha on and off.
Make it accessible (VoiceOver labels and values).
Add a #Preview with realistic sample data.

In Soarias's Build phase, paste this prompt into the active session to scaffold the component directly into your feature branch — Soarias keeps the generated file in context so follow-up tweaks like "disable opacity by default" apply immediately without re-explaining the codebase.

Related

FAQ

Does this work on iOS 16?

ColorPicker itself is available from iOS 14+. The two-argument .onChange(of:perform:) syntax used in this guide requires iOS 17. If you target iOS 16, replace it with the single-closure form: .onChange(of: selectedColor) { newColor in … }. Everything else compiles unchanged.

Can I restrict the color picker to a specific palette?

No — Apple's system ColorPicker always shows the full wheel and cannot be limited to a predefined palette. If you need a restricted palette (e.g., 8 brand swatches), build a custom grid of Circle() views with a selectedColor binding and add a checkmark overlay for the active selection. This is a common pattern for theme-selection UIs.

What's the UIKit equivalent?

UIKit uses UIColorPickerViewController presented modally, with your controller adopting UIColorPickerViewControllerDelegate to receive colorPickerViewControllerDidSelectColor(_:) callbacks. SwiftUI's ColorPicker wraps this internally — so they produce identical sheets, but the SwiftUI version removes all the delegation boilerplate.

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

```