Skip to content

Color Swatch Picker

A grid of selectable color swatches where the chosen one grows a ring.

Color Swatch Picker, playing
Download .swift
Origin
Original work
Found by
color picker · swatches · palette · selection · ring · theme · accent color · selector de color · muestras · paleta · selección · anillo · tema · color de acento

A grid of color swatches where the selected one grows a ring.

With Reduce Motion on, the ring fades in without growing.

struct ColorSwatchPickerDemo: View {
private static let options = [
ColorSwatchPickerOption(name: "Coral", color: .red),
ColorSwatchPickerOption(name: "Amber", color: .orange),
ColorSwatchPickerOption(name: "Mint", color: .mint),
ColorSwatchPickerOption(name: "Sky", color: .cyan),
ColorSwatchPickerOption(name: "Indigo", color: .indigo),
ColorSwatchPickerOption(name: "Rose", color: .pink),
]
@State private var selection = options[2]
var body: some View {
VStack(spacing: 32) {
Image(systemName: "paintpalette.fill")
.font(.largeTitle)
.imageScale(.large)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.frame(height: 220)
.background(selection.color.gradient, in: .rect(cornerRadius: 28))
.animation(.easeInOut(duration: 0.3), value: selection)
.accessibilityHidden(true)
Text("Pick an accent")
.font(.title2.bold())
ColorSwatchPicker(options: Self.options, selection: $selection)
}
.padding(24)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
ParameterTypeDefault
options[ColorSwatchPickerOption]Required
selectionBinding<ColorSwatchPickerOption>Required

Copy this code into your project, or download the file.

ColorSwatchPicker.swift
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors
import SwiftUI
struct ColorSwatchPickerOption: Hashable, Identifiable {
let name: String
let color: Color
var id: String { name }
}
/// A grid of color swatches where the selected one grows a ring.
///
/// With Reduce Motion on, the ring fades in without growing.
struct ColorSwatchPicker: View {
let options: [ColorSwatchPickerOption]
@Binding var selection: ColorSwatchPickerOption
@ScaledMetric(relativeTo: .title) private var swatchSize = 48.0
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum: swatchSize), spacing: 12)], spacing: 12) {
ForEach(options) { option in
swatch(option)
}
}
.animation(reduceMotion ? .easeOut(duration: 0.15) : .spring(duration: 0.35, bounce: 0.5), value: selection)
}
private func swatch(_ option: ColorSwatchPickerOption) -> some View {
let isSelected = option == selection
return Button {
selection = option
} label: {
ZStack {
Circle()
.strokeBorder(option.color, lineWidth: 3)
.scaleEffect(isSelected || reduceMotion ? 1 : 0.5)
.opacity(isSelected ? 1 : 0)
Circle()
.fill(option.color.gradient)
.padding(isSelected ? 8 : 4)
}
.frame(width: swatchSize, height: swatchSize)
.contentShape(.circle)
}
.buttonStyle(.plain)
.accessibilityLabel(option.name)
.accessibilityAddTraits(isSelected ? .isSelected : [])
}
}
// MARK: - Demo
struct ColorSwatchPickerDemo: View {
private static let options = [
ColorSwatchPickerOption(name: "Coral", color: .red),
ColorSwatchPickerOption(name: "Amber", color: .orange),
ColorSwatchPickerOption(name: "Mint", color: .mint),
ColorSwatchPickerOption(name: "Sky", color: .cyan),
ColorSwatchPickerOption(name: "Indigo", color: .indigo),
ColorSwatchPickerOption(name: "Rose", color: .pink),
]
@State private var selection = options[2]
var body: some View {
VStack(spacing: 32) {
Image(systemName: "paintpalette.fill")
.font(.largeTitle)
.imageScale(.large)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.frame(height: 220)
.background(selection.color.gradient, in: .rect(cornerRadius: 28))
.animation(.easeInOut(duration: 0.3), value: selection)
.accessibilityHidden(true)
Text("Pick an accent")
.font(.title2.bold())
ColorSwatchPicker(options: Self.options, selection: $selection)
}
.padding(24)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
#Preview {
ColorSwatchPickerDemo()
}