Skip to content

Blurred Image Backdrop

A full-screen backdrop made from a blurred image with a tint layer, for placing content on top of it.

Blurred Image Backdrop, playing
Download .swift
Origin
Original work
Found by
blur · backdrop · background · image · album art · tint · photo · desenfoque · fondo · imagen · carátula · tinte · foto

A full-screen backdrop made from a blurred image with a tint on top, for placing content over it.

It is decorative, so VoiceOver skips it.

struct BlurredImageBackdropDemo: View {
private struct Album {
let title: String
let artist: String
let symbol: String
let colors: [Color]
var artwork: Image {
Image(size: CGSize(width: 600, height: 600)) { context in
let bounds = CGRect(x: 0, y: 0, width: 600, height: 600)
context.fill(
Path(bounds),
with: .linearGradient(Gradient(colors: colors), startPoint: .zero, endPoint: CGPoint(x: 600, y: 600))
)
var symbolImage = context.resolve(Image(systemName: symbol).resizable())
symbolImage.shading = .color(.white.opacity(0.9))
context.draw(symbolImage, in: bounds.insetBy(dx: 170, dy: 170))
}
}
}
private let albums = [
Album(title: "Solar Tide", artist: "The Longwaves", symbol: "sun.haze.fill", colors: [.orange, .pink]),
Album(title: "Night Forest", artist: "Moss & Ember", symbol: "leaf.fill", colors: [.teal, .indigo]),
Album(title: "Violet Hours", artist: "Paper Planes", symbol: "moon.stars.fill", colors: [.purple, .blue]),
]
@State private var index = 0
@State private var isAutoPlaying = true
@Environment(\.accessibilityReduceMotion) private var reduceMotion
private var album: Album { albums[index] }
var body: some View {
ZStack {
BlurredImageBackdrop(image: album.artwork)
.id(index)
.transition(.opacity)
ScrollView {
VStack(spacing: 24) {
album.artwork
.resizable()
.scaledToFit()
.frame(maxWidth: 260)
.clipShape(.rect(cornerRadius: 28))
.shadow(color: .black.opacity(0.35), radius: 24, y: 14)
.id(index)
.transition(.opacity)
.accessibilityHidden(true)
VStack(spacing: 4) {
Text(album.title)
.font(.system(.title, design: .rounded, weight: .bold))
Text(album.artist)
.font(.body)
.opacity(0.85)
}
.multilineTextAlignment(.center)
Button("Next album", systemImage: "forward.fill") {
isAutoPlaying = false
index = (index + 1) % albums.count
}
.buttonStyle(.bordered)
}
.foregroundStyle(.white)
.padding(24)
.frame(maxWidth: .infinity)
}
.defaultScrollAnchor(.center, for: .alignment)
.tint(.white)
}
.animation(reduceMotion ? nil : .easeInOut(duration: 0.8), value: index)
.task(id: reduceMotion) {
guard !reduceMotion else { return }
while isAutoPlaying {
try? await Task.sleep(for: .seconds(1.6))
guard !Task.isCancelled, isAutoPlaying else { return }
index = (index + 1) % albums.count
}
}
}
}
ParameterTypeDefault
imageImageRequired
blurRadiusCGFloat40
tintColor.black
tintOpacityDouble0.35

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

BlurredImageBackdrop.swift
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors
import SwiftUI
/// A full-screen backdrop made from a blurred image with a tint on top, for placing content over it.
///
/// It is decorative, so VoiceOver skips it.
struct BlurredImageBackdrop: View {
let image: Image
var blurRadius: CGFloat = 40
var tint: Color = .black
var tintOpacity: Double = 0.35
var body: some View {
Color.clear
.overlay {
image
.resizable()
.scaledToFill()
.blur(radius: max(blurRadius, 0), opaque: true)
}
.overlay(tint.opacity(min(max(tintOpacity, 0), 1)))
.clipped()
.ignoresSafeArea()
.accessibilityRepresentation { EmptyView() }
}
}
// MARK: - Demo
struct BlurredImageBackdropDemo: View {
private struct Album {
let title: String
let artist: String
let symbol: String
let colors: [Color]
var artwork: Image {
Image(size: CGSize(width: 600, height: 600)) { context in
let bounds = CGRect(x: 0, y: 0, width: 600, height: 600)
context.fill(
Path(bounds),
with: .linearGradient(Gradient(colors: colors), startPoint: .zero, endPoint: CGPoint(x: 600, y: 600))
)
var symbolImage = context.resolve(Image(systemName: symbol).resizable())
symbolImage.shading = .color(.white.opacity(0.9))
context.draw(symbolImage, in: bounds.insetBy(dx: 170, dy: 170))
}
}
}
private let albums = [
Album(title: "Solar Tide", artist: "The Longwaves", symbol: "sun.haze.fill", colors: [.orange, .pink]),
Album(title: "Night Forest", artist: "Moss & Ember", symbol: "leaf.fill", colors: [.teal, .indigo]),
Album(title: "Violet Hours", artist: "Paper Planes", symbol: "moon.stars.fill", colors: [.purple, .blue]),
]
@State private var index = 0
@State private var isAutoPlaying = true
@Environment(\.accessibilityReduceMotion) private var reduceMotion
private var album: Album { albums[index] }
var body: some View {
ZStack {
BlurredImageBackdrop(image: album.artwork)
.id(index)
.transition(.opacity)
ScrollView {
VStack(spacing: 24) {
album.artwork
.resizable()
.scaledToFit()
.frame(maxWidth: 260)
.clipShape(.rect(cornerRadius: 28))
.shadow(color: .black.opacity(0.35), radius: 24, y: 14)
.id(index)
.transition(.opacity)
.accessibilityHidden(true)
VStack(spacing: 4) {
Text(album.title)
.font(.system(.title, design: .rounded, weight: .bold))
Text(album.artist)
.font(.body)
.opacity(0.85)
}
.multilineTextAlignment(.center)
Button("Next album", systemImage: "forward.fill") {
isAutoPlaying = false
index = (index + 1) % albums.count
}
.buttonStyle(.bordered)
}
.foregroundStyle(.white)
.padding(24)
.frame(maxWidth: .infinity)
}
.defaultScrollAnchor(.center, for: .alignment)
.tint(.white)
}
.animation(reduceMotion ? nil : .easeInOut(duration: 0.8), value: index)
.task(id: reduceMotion) {
guard !reduceMotion else { return }
while isAutoPlaying {
try? await Task.sleep(for: .seconds(1.6))
guard !Task.isCancelled, isAutoPlaying else { return }
index = (index + 1) % albums.count
}
}
}
}
#Preview {
BlurredImageBackdropDemo()
}