swiftui-colors-modifiers
SwiftUI Colors and Modifiers
Comprehensive guide to modern SwiftUI color APIs, ShapeStyle, gradients, and creating reusable ViewModifiers for iOS 26.
Prerequisites
- iOS 15+ for foregroundStyle (iOS 26 recommended)
- Xcode 26+
Modern Color APIs
foregroundStyle (Recommended)
Replaces the deprecated foregroundColor(_:):
// DEPRECATED
Text("Hello")
.foregroundColor(.blue)
// MODERN - Use foregroundStyle
Text("Hello")
.foregroundStyle(.blue)
// With gradients
Text("Gradient Text")
.foregroundStyle(
LinearGradient(
colors: [.blue, .purple],
startPoint: .leading,
endPoint: .trailing
)
)
ShapeStyle Protocol
foregroundStyle accepts any ShapeStyle:
// Colors
.foregroundStyle(.red)
.foregroundStyle(Color.blue)
// Gradients
.foregroundStyle(LinearGradient(...))
.foregroundStyle(RadialGradient(...))
.foregroundStyle(AngularGradient(...))
.foregroundStyle(MeshGradient(...))
// Materials
.foregroundStyle(.ultraThinMaterial)
.foregroundStyle(.regularMaterial)
// Hierarchical
.foregroundStyle(.primary)
.foregroundStyle(.secondary)
.foregroundStyle(.tertiary)
Hierarchical Colors
Setting Hierarchy at Root
// Set all three levels at once
ContentView()
.foregroundStyle(.red, .orange, .yellow)
// Children use hierarchical levels
struct ContentView: View {
var body: some View {
VStack {
Text("Primary") // Red
.foregroundStyle(.primary)
Text("Secondary") // Orange
.foregroundStyle(.secondary)
Text("Tertiary") // Yellow
.foregroundStyle(.tertiary)
}
}
}
Available Levels
.foregroundStyle(.primary) // Level 1 - Most prominent
.foregroundStyle(.secondary) // Level 2
.foregroundStyle(.tertiary) // Level 3
.foregroundStyle(.quaternary) // Level 4
.foregroundStyle(.quinary) // Level 5 - Least prominent
Note: Only first three can be customized via foregroundStyle(_:_:_:).
Practical Example
struct CardView: View {
let title: String
let subtitle: String
let detail: String
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.headline)
.foregroundStyle(.primary)
Text(subtitle)
.font(.subheadline)
.foregroundStyle(.secondary)
Text(detail)
.font(.caption)
.foregroundStyle(.tertiary)
}
}
}
Semantic Colors
System Colors
// Adaptive colors (change with Dark Mode)
Color.primary // Black/White
Color.secondary // Gray
Color.accentColor // App's accent color
// UI element colors
Color(uiColor: .systemBackground)
Color(uiColor: .secondarySystemBackground)
Color(uiColor: .tertiarySystemBackground)
Color(uiColor: .label)
Color(uiColor: .secondaryLabel)
Accent Color
Set in Asset Catalog or programmatically:
// In code
Button("Action") { }
.tint(.blue)
// App-wide in Assets.xcassets:
// Create "AccentColor" color set
Tint Modifier
Override accent color for a hierarchy:
// tint affects interactive elements, not all foreground
VStack {
Button("Blue") { } // Uses blue tint
Link("Website", destination: url) // Uses blue tint
Text("Plain") // NOT affected by tint
}
.tint(.blue)
tint vs foregroundStyle:
tint: Affects buttons, links, controlsforegroundStyle: Affects all foreground content
Gradients
LinearGradient
LinearGradient(
colors: [.blue, .purple],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
// With stops for control
LinearGradient(
stops: [
.init(color: .red, location: 0),
.init(color: .orange, location: 0.3),
.init(color: .yellow, location: 1)
],
startPoint: .top,
endPoint: .bottom
)
// Usage
Rectangle()
.fill(
LinearGradient(
colors: [.blue, .cyan],
startPoint: .leading,
endPoint: .trailing
)
)
RadialGradient
RadialGradient(
colors: [.white, .blue],
center: .center,
startRadius: 0,
endRadius: 200
)
// With offset center
RadialGradient(
colors: [.yellow, .orange, .red],
center: .topLeading,
startRadius: 50,
endRadius: 300
)
AngularGradient
AngularGradient(
colors: [.red, .yellow, .green, .blue, .purple, .red],
center: .center
)
// Conic gradient with angle
AngularGradient(
colors: [.blue, .purple],
center: .center,
startAngle: .degrees(0),
endAngle: .degrees(180)
)
MeshGradient (iOS 18+)
Complex multi-point gradients:
MeshGradient(
width: 3,
height: 3,
points: [
// Row 0
[0.0, 0.0], [0.5, 0.0], [1.0, 0.0],
// Row 1
[0.0, 0.5], [0.5, 0.5], [1.0, 0.5],
// Row 2
[0.0, 1.0], [0.5, 1.0], [1.0, 1.0]
],
colors: [
.red, .orange, .yellow,
.green, .blue, .purple,
.pink, .cyan, .mint
]
)
// Animated mesh
struct AnimatedMesh: View {
@State private var offset: CGFloat = 0
var body: some View {
MeshGradient(
width: 3,
height: 3,
points: [
[0.0, 0.0], [0.5, 0.0], [1.0, 0.0],
[0.0, 0.5], [0.5 + offset, 0.5], [1.0, 0.5],
[0.0, 1.0], [0.5, 1.0], [1.0, 1.0]
],
colors: [
.blue, .cyan, .teal,
.purple, .indigo, .blue,
.pink, .orange, .yellow
],
smoothsColors: true
)
.onAppear {
withAnimation(.easeInOut(duration: 2).repeatForever()) {
offset = 0.2
}
}
}
}
Asset Catalog Colors
Creating Color Sets
- Open Assets.xcassets
- Right-click → New Color Set
- Configure for appearances:
- Any Appearance
- Light
- Dark
- High Contrast variants
Using Asset Colors
// By name
Color("BrandPrimary")
Color("BackgroundColor")
// With bundle
Color("CustomColor", bundle: .module)
Organizing Colors
Assets.xcassets/
├── Colors/
│ ├── Brand/
│ │ ├── BrandPrimary
│ │ ├── BrandSecondary
│ │ └── BrandAccent
│ ├── UI/
│ │ ├── BackgroundPrimary
│ │ ├── BackgroundSecondary
│ │ └── SeparatorColor
│ └── Text/
│ ├── TextPrimary
│ ├── TextSecondary
│ └── TextTertiary
Custom ShapeStyles (iOS 17+)
struct StripedStyle: ShapeStyle {
var color1: Color
var color2: Color
var stripeWidth: CGFloat
func resolve(in environment: EnvironmentValues) -> some ShapeStyle {
// Return a resolved style
LinearGradient(
stops: generateStripeStops(),
startPoint: .leading,
endPoint: .trailing
)
}
private func generateStripeStops() -> [Gradient.Stop] {
var stops: [Gradient.Stop] = []
var position: CGFloat = 0
while position < 1 {
stops.append(.init(color: color1, location: position))
stops.append(.init(color: color1, location: position + stripeWidth / 2))
stops.append(.init(color: color2, location: position + stripeWidth / 2))
stops.append(.init(color: color2, location: position + stripeWidth))
position += stripeWidth
}
return stops
}
}
// Usage
Rectangle()
.fill(StripedStyle(color1: .blue, color2: .white, stripeWidth: 0.1))
Custom ViewModifiers
Basic Modifier
struct CardStyle: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(radius: 4)
}
}
extension View {
func cardStyle() -> some View {
modifier(CardStyle())
}
}
// Usage
Text("Card Content")
.cardStyle()
Configurable Modifier
struct RoundedStyle: ViewModifier {
var cornerRadius: CGFloat
var backgroundColor: Color
var shadowRadius: CGFloat
func body(content: Content) -> some View {
content
.padding()
.background(backgroundColor)
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
.shadow(radius: shadowRadius)
}
}
extension View {
func rounded(
cornerRadius: CGFloat = 12,
backgroundColor: Color = .white,
shadowRadius: CGFloat = 4
) -> some View {
modifier(RoundedStyle(
cornerRadius: cornerRadius,
backgroundColor: backgroundColor,
shadowRadius: shadowRadius
))
}
}
// Usage
Text("Custom")
.rounded(cornerRadius: 20, backgroundColor: .blue)
Environment-Aware Modifier
struct AdaptiveCard: ViewModifier {
@Environment(\.colorScheme) var colorScheme
func body(content: Content) -> some View {
content
.padding()
.background(colorScheme == .dark ? Color.gray.opacity(0.2) : Color.white)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(
color: colorScheme == .dark ? .clear : .black.opacity(0.1),
radius: 8
)
}
}
Conditional Modifier
extension View {
@ViewBuilder
func `if`<Content: View>(
_ condition: Bool,
transform: (Self) -> Content
) -> some View {
if condition {
transform(self)
} else {
self
}
}
@ViewBuilder
func ifLet<T, Content: View>(
_ value: T?,
transform: (Self, T) -> Content
) -> some View {
if let value {
transform(self, value)
} else {
self
}
}
}
// Usage
Text("Hello")
.if(isHighlighted) { view in
view.foregroundStyle(.yellow)
}
.ifLet(user) { view, user in
view.badge(user.notificationCount)
}
iOS 26 New Modifiers
Close Button Role
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Dismiss", role: .close) {
dismiss()
}
// Renders as glass X button
}
}
Glass Button Styles
Button("Glass") { }
.buttonStyle(.glass)
Button("Prominent") { }
.buttonStyle(.glassProminent)
Custom Slider Ticks
Slider(value: $value, in: 0...100) {
Text("Value")
} minimumValueLabel: {
Text("0")
} maximumValueLabel: {
Text("100")
}
.sliderStyle(.ticked(count: 10)) // iOS 26
Design System Example
// DesignSystem.swift
enum DS {
enum Colors {
static let primary = Color("Primary")
static let secondary = Color("Secondary")
static let background = Color("Background")
static let surface = Color("Surface")
static let error = Color("Error")
static let success = Color("Success")
}
enum Spacing {
static let xs: CGFloat = 4
static let sm: CGFloat = 8
static let md: CGFloat = 16
static let lg: CGFloat = 24
static let xl: CGFloat = 32
}
enum CornerRadius {
static let sm: CGFloat = 4
static let md: CGFloat = 8
static let lg: CGFloat = 16
static let xl: CGFloat = 24
}
}
// Modifiers using design system
struct DSCard: ViewModifier {
func body(content: Content) -> some View {
content
.padding(DS.Spacing.md)
.background(DS.Colors.surface)
.clipShape(RoundedRectangle(cornerRadius: DS.CornerRadius.lg))
}
}
extension View {
func dsCard() -> some View {
modifier(DSCard())
}
}
Best Practices
- Use foregroundStyle - Not deprecated foregroundColor
- Leverage Hierarchical Colors - .primary, .secondary, .tertiary
- Asset Catalog for Themes - Organize colors properly
- DRY with Modifiers - Create reusable ViewModifiers
- Compose Modifiers - Build complex styles from simple ones
- Environment Awareness - Respect colorScheme and accessibility
Official Resources
More from bluewaves-creations/bluewaves-skills
photographer-testino
Generate images in Mario Testino's glamorous vibrant style. Use when users ask for Testino style, high fashion glamour, bold saturated colors, warm luxurious photography, dynamic sensual energy.
35photographer-lindbergh
Generate images in Peter Lindbergh's iconic black and white style. Use when users ask for Lindbergh style, raw authentic beauty, emotional B&W portraits, supermodel aesthetic, or unretouched natural photography.
30photographer-lachapelle
Generate images in David LaChapelle's surreal pop art style. Use when users ask for LaChapelle style, pop surrealism, hyper-saturated colors, theatrical staging, baroque maximalism, kitsch aesthetic.
24epub-creator
Create production-quality EPUB 3 ebooks from markdown and images with automated QA, formatting fixes, and validation. Use when creating ebooks, converting markdown to EPUB, or compiling chapters into a publishable book. Handles markdown quirks, generates TOC, adds covers, and validates output automatically.
22photographer-vonunwerth
Generate images in Ellen von Unwerth's playful vintage style. Use when users ask for von Unwerth style, playful sensuality, vintage film noir, whimsical feminine photography, retro glamour, narrative storytelling.
19photographer-ritts
Generate images in Herb Ritts' sculptural black and white style. Use when users ask for Ritts style, classical Greek aesthetic, sculptural body photography, California golden hour, minimalist athletic portraits.
18