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

Shimmer Button

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

A black pill button with a specular highlight that sweeps across the label.

Buttons · SwiftUI · Flutter · iOS 17 · Flutter 3.27

  • button
  • shimmer
  • cta
  • gradient
  • animated

The actual source

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

import SwiftUI

/// A pill button with a specular highlight that sweeps across it.
///
/// The sweep is a single masked gradient driven by one repeating animation —
/// no timer, no `TimelineView`, nothing to tear down when the view disappears.
/// The static gloss on top means the button still reads as glossy when motion
/// is disabled.
struct ShimmerButton: View {
    var title: String = "Get early access"
    var action: () -> Void = {}
    /// Drive the sweep yourself with 0...1 instead of letting it animate.
    var sweepPhase: Double? = nil

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

    private var phase: CGFloat {
        if let sweepPhase { return -1.4 + CGFloat(sweepPhase) * 2.8 }
        return animatedPhase
    }

    private let shape = Capsule(style: .continuous)

    var body: some View {
        Button(action: action) {
            Text(title)
                .font(.system(size: 16, weight: .semibold))
                .foregroundStyle(.white)
                .padding(.vertical, 16)
                .padding(.horizontal, 34)
        }
        .buttonStyle(PressScale())
        .background(Color.black, in: shape)
        .overlay {
            // Static gloss: a soft highlight along the top edge.
            shape
                .fill(
                    LinearGradient(
                        colors: [.white.opacity(0.22), .clear],
                        startPoint: .top,
                        endPoint: .center
                    )
                )
                .allowsHitTesting(false)
        }
        .overlay {
            // Travelling sweep, clipped to the pill.
            GeometryReader { geo in
                shape
                    .fill(
                        LinearGradient(
                            stops: [
                                .init(color: .white.opacity(0), location: 0),
                                .init(color: .white.opacity(0.5), location: 0.5),
                                .init(color: .white.opacity(0), location: 1),
                            ],
                            startPoint: .leading,
                            endPoint: .trailing
                        )
                    )
                    .frame(width: geo.size.width * 0.6)
                    .blur(radius: 5)
                    .offset(x: phase * geo.size.width)
            }
            .clipShape(shape)
            .allowsHitTesting(false)
        }
        .overlay(shape.strokeBorder(.white.opacity(0.14), lineWidth: 1))
        .onAppear(perform: startSweep)
    }

    private func startSweep() {
        guard !reduceMotion, sweepPhase == nil else { return }
        withAnimation(.linear(duration: 2.4).repeatForever(autoreverses: false)) {
            animatedPhase = 1.4
        }
    }
}

/// Quick, small press feedback. Copy this alongside the button.
private struct PressScale: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.97 : 1)
            .animation(.spring(response: 0.28, dampingFraction: 0.7),
                       value: configuration.isPressed)
    }
}

#Preview {
    ShimmerButton()
        .padding(40)
}
iOS 17 · No dependencies

SwiftUI note. The sweep uses one repeating animation on a masked gradient. It stops entirely under Reduce Motion, leaving the static gloss.

Dependencies

SwiftUI
No external dependencies

Requires iOS 17

Flutter
No external dependencies

Requires Flutter 3.27