Skip to content

Empty State Placeholder

An empty screen with a breathing symbol, a message and an optional call to action.

Empty State Placeholder, playing
Download .swift
Origin
Original work
Found by
empty state · placeholder · no content · symbol · call to action · content unavailable · estado vacío · marcador · sin contenido · símbolo · llamada a la acción · contenido no disponible

An empty screen with a breathing symbol, a message and an optional call to action.

With Reduce Motion on, the symbol stays still.

struct EmptyStatePlaceholderDemo: View {
@State private var messages: [String] = []
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View {
ZStack {
if messages.isEmpty {
EmptyStatePlaceholder(
systemImage: "tray",
title: "No messages",
message: "New conversations will show up here.",
actionTitle: "Load samples"
) {
withAnimation(reduceMotion ? nil : .snappy) {
messages = ["Welcome aboard", "Your invoice is ready", "Lunch on Friday?"]
}
}
.transition(.opacity)
} else {
List {
ForEach(messages, id: \.self) { message in
Label(message, systemImage: "envelope")
}
Button("Clear all", role: .destructive) {
withAnimation(reduceMotion ? nil : .snappy) { messages = [] }
}
}
.transition(.opacity)
}
}
}
}
ParameterTypeDefault
systemImageStringRequired
titleLocalizedStringResourceRequired
messageLocalizedStringResourceRequired
actionTitleLocalizedStringResource?nil
action(() -> Void)?nil

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

EmptyStatePlaceholder.swift
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors
import SwiftUI
/// An empty screen with a breathing symbol, a message and an optional call to action.
///
/// With Reduce Motion on, the symbol stays still.
struct EmptyStatePlaceholder: View {
var systemImage: String
var title: LocalizedStringResource
var message: LocalizedStringResource
var actionTitle: LocalizedStringResource?
var action: (() -> Void)?
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View {
ContentUnavailableView {
Label {
Text(title)
} icon: {
Image(systemName: systemImage)
.symbolEffect(.breathe, isActive: !reduceMotion)
}
} description: {
Text(message)
} actions: {
if let actionTitle, let action {
Button(actionTitle, action: action)
.buttonStyle(.borderedProminent)
}
}
}
}
// MARK: - Demo
struct EmptyStatePlaceholderDemo: View {
@State private var messages: [String] = []
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View {
ZStack {
if messages.isEmpty {
EmptyStatePlaceholder(
systemImage: "tray",
title: "No messages",
message: "New conversations will show up here.",
actionTitle: "Load samples"
) {
withAnimation(reduceMotion ? nil : .snappy) {
messages = ["Welcome aboard", "Your invoice is ready", "Lunch on Friday?"]
}
}
.transition(.opacity)
} else {
List {
ForEach(messages, id: \.self) { message in
Label(message, systemImage: "envelope")
}
Button("Clear all", role: .destructive) {
withAnimation(reduceMotion ? nil : .snappy) { messages = [] }
}
}
.transition(.opacity)
}
}
}
}
#Preview {
EmptyStatePlaceholderDemo()
}