
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
- Flutter
- No external dependencies
Requires iOS 17
Requires Flutter 3.27


