swiftui-colors-modifiers

SKILL.md

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, controls
  • foregroundStyle: 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

  1. Open Assets.xcassets
  2. Right-click → New Color Set
  3. 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

  1. Use foregroundStyle - Not deprecated foregroundColor
  2. Leverage Hierarchical Colors - .primary, .secondary, .tertiary
  3. Asset Catalog for Themes - Organize colors properly
  4. DRY with Modifiers - Create reusable ViewModifiers
  5. Compose Modifiers - Build complex styles from simple ones
  6. Environment Awareness - Respect colorScheme and accessibility

Official Resources

Weekly Installs
6
GitHub Stars
1
First Seen
Jan 26, 2026
Installed on
opencode6
claude-code5
codex5
gemini-cli5
continue4
qwen-code4