Skip to content
Mesh Gradient preview
Painted live in the browser. The committed still and loop are rendered from the SwiftUI source.

Mesh Gradient

From syxUI — written for both platforms, not translated between them.

A soft nine-point colour field, gently warping. Native MeshGradient on iOS 18.

Backgrounds · SwiftUI · Flutter · iOS 17 · Flutter 3.27

  • background
  • mesh
  • gradient
  • colour
  • ambient
  • soft

The actual source

MeshBackground.swift
// Mesh Gradient · syxUI · https://syxui.dev/components/mesh-gradient
// Free in any project. Keep this line and credit syxUI where a person can read it.

import SwiftUI

/// A soft mesh gradient field.
///
/// Uses SwiftUI's native `MeshGradient` on iOS 18 and falls back to layered
/// radial gradients before that, so it renders on every supported OS without
/// a branch at the call site.
struct MeshBackground: View {
    var colors: [Color] = [
        Color(red: 0.99, green: 0.85, blue: 0.72), Color(red: 0.98, green: 0.72, blue: 0.80), Color(red: 0.85, green: 0.74, blue: 0.99),
        Color(red: 0.99, green: 0.78, blue: 0.66), Color(red: 0.93, green: 0.80, blue: 0.98), Color(red: 0.72, green: 0.82, blue: 0.99),
        Color(red: 0.98, green: 0.88, blue: 0.78), Color(red: 0.78, green: 0.88, blue: 0.99), Color(red: 0.70, green: 0.90, blue: 0.94),
    ]
    var animated: Bool = true
    var period: Double = 9
    /// Drive the warp yourself with 0...1 instead of letting it animate.
    var phase: Double? = nil

    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    @State private var animatedWarp: Float = 0

    /// Mapped through a cosine so t = 0 and t = 1 are the same frame.
    private var warp: Float {
        if let phase { return Float((1 - cos(phase * 2 * .pi)) / 2) }
        return animatedWarp
    }

    var body: some View {
        Group {
            if #available(iOS 18.0, macOS 15.0, *) {
                MeshGradient(width: 3, height: 3, points: points, colors: colors)
            } else {
                fallback
            }
        }
        .onAppear {
            guard animated, !reduceMotion, phase == nil else { return }
            withAnimation(.easeInOut(duration: period).repeatForever(autoreverses: true)) {
                animatedWarp = 1
            }
        }
        .accessibilityHidden(true)
    }

    /// A 3×3 control lattice. Only the middle row and column move, which keeps
    /// the corners pinned and stops the field from sliding off its own edges.
    private var points: [SIMD2<Float>] {
        let mid = 0.5 + warp * 0.26
        let low = 0.5 - warp * 0.22

        return [
            .init(0, 0), .init(Float(low), 0), .init(1, 0),
            .init(0, 0.5), .init(Float(mid), Float(mid)), .init(1, Float(low)),
            .init(0, 1), .init(Float(mid), 1), .init(1, 1),
        ]
    }

    private var fallback: some View {
        ZStack {
            colors.first ?? .clear
            ForEach(Array(colors.enumerated()), id: \.offset) { index, color in
                let column = Double(index % 3) / 2
                let row = Double(index / 3) / 2

                RadialGradient(
                    colors: [color.opacity(0.9), color.opacity(0)],
                    center: UnitPoint(x: column, y: row),
                    startRadius: 0,
                    endRadius: 220
                )
            }
        }
        .blur(radius: 28)
        .clipped()
    }
}

#Preview {
    MeshBackground()
        .frame(width: 400, height: 300)
}
iOS 17 · No dependencies

SwiftUI note. MeshGradient is iOS 18+. The included fallback keeps iOS 17 working, so the call site never needs an availability check.

Dependencies

SwiftUI
No external dependencies

Requires iOS 17

Flutter
No external dependencies

Requires Flutter 3.27