How to create mesh gradient in iOS 18

Issue #994

With iOS 18, Apple introduced a powerful new tool for creating beautiful, dynamic backgrounds: MeshGradient. Forget simple two-color gradients; mesh gradients let you blend a whole grid of colors together smoothly, creating stunning, cloud-like effects.

Think of a mesh gradient as a flexible, colorful net. To create one, you just need to define its structure and colors.

The first step is to define the size of your net. You do this with width and height. This tells the gradient how many points your grid will have.

  • A width of 2 and height of 2 gives you a 2x2 grid (4 points total).
  • A width of 3 and height of 3 gives you a 3x3 grid (9 points total), like in our fancy counter demo.

Next, you need to tell each color where to sit on the grid. The points array holds the coordinates for each color. These coordinates are relative, ranging from (0, 0) (top-left corner) to (1, 1) (bottom-right corner).

For a 3x3 grid, you’ll provide 9 points, starting from the top-left, moving across each row.

Define colors

This is the fun part! The colors array is a simple list of all the Colors you want to use. The number of colors must match the number of points in your grid (width * height). The colors are applied to the points in the order you list them.

Mesh as foreground style

Here’s how you’d create a simple, non-moving 2x2 mesh gradient.

struct ForegroundMesh: View {
    static let initialPoints: [SIMD2<Float>] = [
        .init(x: 0, y: 0), .init(x: 1, y: 0),
        .init(x: 0, y: 1), .init(x: 1, y: 1)
    ]

    var body: some View {
        VStack {
            Image(systemName: "sparkles")
                .font(.system(size: 60, weight: .black, design: .rounded))

            Text("Hello, Mesh!")
                .font(.system(size: 80, weight: .black, design: .rounded))
        }
        .foregroundStyle(
            MeshGradient(
                width: 2,
                height: 2,
                points: Self.initialPoints,
                colors: [.purple, .pink, .red, .orange]
            )
        )
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(.systemBackground))
    }
}

Animating the mesh

We can create mesmerizing, fluid backgrounds by changing the points over time. The easiest way to do this is with a TimelineView.

A TimelineView redraws its content on a schedule, which is perfect for animation. We can use its schedule to constantly calculate new positions for our gradient’s points.

struct TimelineMesh: View {
    @State private var points = TimelineMesh.initialPoints

    let colors: [Color] = [
        .purple, .pink, .red,
        .orange, .yellow, .green,
        .cyan, .blue, .indigo
    ]

    static let initialPoints: [SIMD2<Float>] = [
        // Row 1
        .init(x: 0, y: 0), .init(x: 0.5, y: 0), .init(x: 1, y: 0),
        // Row 2
        .init(x: 0, y: 0.5), .init(x: 0.5, y: 0.5), .init(x: 1, y: 0.5),
        // Row 3
        .init(x: 0, y: 1), .init(x: 0.5, y: 1), .init(x: 1, y: 1)
    ]

    var body: some View {
        TimelineView(.animation) { context in
            MeshGradient(
                width: 3,
                height: 3,
                points: points,
                colors: colors
            )
            .ignoresSafeArea()
            .onChange(of: context.date) {
                withAnimation(.easeInOut(duration: 2.0)) {
                    updatePoints(time: context.date.timeIntervalSince1970)
                }
            }
        }
    }
}
Written by

I’m open source contributor, writer, speaker and product maker.

Start the conversation