Skip to content
Skeleton Shimmer preview
An animated render of the SwiftUI source on this page.

Skeleton Shimmer

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

Placeholder blocks with a sweeping highlight, sized to the real content.

Loaders · SwiftUI · Flutter · iOS 17 · Flutter 3.27

  • loader
  • skeleton
  • shimmer
  • placeholder
  • loading
  • list

The actual source

SkeletonShimmer.swift
// Skeleton Shimmer · syxUI · https://syxui.dev/components/skeleton-shimmer
// Free in any project. Keep this line and credit syxUI where a person can read it.

import SwiftUI

/// Placeholder blocks with a highlight sweeping across them.
///
/// `SkeletonBlock` is the reusable piece; `SkeletonShimmer` is a ready-made
/// list row built from three of them. Match the block sizes to the real
/// content so the layout does not jump when data arrives.
struct SkeletonShimmer: View {
    /// Drive the sweep yourself with 0...1 instead of letting it animate.
    var phase: Double? = nil

    var body: some View {
        HStack(spacing: 14) {
            SkeletonBlock(width: 54, height: 54, cornerRadius: 16, phase: phase)

            VStack(alignment: .leading, spacing: 8) {
                SkeletonBlock(width: 150, height: 13, phase: phase)
                SkeletonBlock(width: 210, height: 11, phase: phase)
                SkeletonBlock(width: 96, height: 11, phase: phase)
            }

            Spacer(minLength: 0)
        }
        .frame(width: 300)
        .accessibilityElement()
        .accessibilityLabel("Loading content")
    }
}

struct SkeletonBlock: View {
    var width: CGFloat? = nil
    var height: CGFloat = 12
    var cornerRadius: CGFloat = 6
    var phase: Double? = nil

    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    @State private var animatedPhase: CGFloat = -1

    private var sweep: CGFloat {
        if let phase { return -1 + CGFloat(phase) * 2 }
        return animatedPhase
    }

    var body: some View {
        RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
            .fill(Color(white: 0.91))
            .frame(width: width, height: height)
            .overlay {
                GeometryReader { geo in
                    LinearGradient(
                        colors: [.clear, .white.opacity(0.85), .clear],
                        startPoint: .leading,
                        endPoint: .trailing
                    )
                    .frame(width: geo.size.width * 0.7)
                    .offset(x: sweep * geo.size.width * 1.6)
                }
                .clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
            }
            .onAppear {
                guard !reduceMotion, phase == nil else { return }
                withAnimation(.linear(duration: 1.3).repeatForever(autoreverses: false)) {
                    animatedPhase = 1
                }
            }
    }
}

#Preview {
    SkeletonShimmer()
        .padding(30)
}
iOS 17 · No dependencies

SwiftUI note. SkeletonBlock is the reusable unit — build your own placeholder rows from it rather than reshaping this one.

Dependencies

SwiftUI
No external dependencies

Requires iOS 17

Flutter
No external dependencies

Requires Flutter 3.27