Skip to content
Glass Sheen preview
An animated render of the SwiftUI source on this page.

Glass Sheen

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

A frosted panel with a stacked-shadow depth and a specular band that sweeps across the surface.

Cards · SwiftUI · Flutter · iOS 17 · Flutter 3.27

  • card
  • glass
  • specular
  • material
  • blur
  • shadow
  • depth
  • frosted

The actual source

DepthGlassSheen.swift
// Glass Sheen · syxUI · https://syxui.dev/components/depth-glass-sheen
// Free in any project. Keep this line and credit syxUI where a person can read it.

import SwiftUI

/// A frosted panel with real, stacked-shadow depth and a specular band that
/// sweeps across the surface — distinct from a static rim-only glass card on
/// three counts: a three-layer shadow stack instead of one blur, a travelling
/// sheen instead of a fixed highlight, and a 1pt inner top line under the rim
/// the way a real glass edge catches light twice.
struct DepthGlassSheen: View {
    var tint: Color = Color(white: 1.0)
    var rimColor: Color = Color(white: 1.0)
    var sheenColor: Color = Color(white: 1.0)
    var shadowColor: Color = Color(red: 0.043, green: 0.051, blue: 0.078)
    /// SwiftUI's `Material` has no public blur radius, so this shapes the
    /// ambient bloom bleeding through the glass instead — see `notes`.
    var blurRadius: CGFloat = 24
    /// Seconds for one full sweep-and-dwell cycle.
    var period: Double = 5.5
    /// Preview override. When set, the component renders that point of one
    /// loop instead of driving its own clock.
    var phase: Double? = nil

    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    @State private var animated: Double = 0

    private let corner: CGFloat = 24

    private var current: Double { phase ?? animated }

    private var shape: RoundedRectangle {
        RoundedRectangle(cornerRadius: corner, style: .continuous)
    }

    var body: some View {
        content
            .padding(20)
            .frame(width: 268, height: 156, alignment: .leading)
            .background(.ultraThinMaterial, in: shape)
            .background { shape.fill(tint.opacity(0.08)) }
            .background {
                // Ambient bloom sitting behind everything else, so the glass
                // and its material both soften it further — this is what
                // `blurRadius` actually drives (see the property comment).
                Circle()
                    .fill(tint.opacity(0.55))
                    .frame(width: 220, height: 150)
                    .blur(radius: blurRadius)
            }
            .overlay { sheenOverlay }
            .overlay {
                shape.strokeBorder(
                    LinearGradient(
                        colors: [rimColor.opacity(0.65), rimColor.opacity(0.12)],
                        startPoint: .top,
                        endPoint: .bottom
                    ),
                    lineWidth: 1
                )
            }
            .overlay {
                // The inner line under the rim — one point further in, faint
                // white — is what real glass edges do: two catches of light,
                // not one.
                shape.inset(by: 1.5).stroke(Color.white.opacity(0.5), lineWidth: 1)
            }
            .clipShape(shape)
            // Three shadows compose in the order written, contact to ambient —
            // one blur reads as a drop shadow, three read as depth.
            .shadow(color: shadowColor.opacity(0.06), radius: 2, x: 0, y: 1)
            .shadow(color: shadowColor.opacity(0.10), radius: 8, x: 0, y: 6)
            .shadow(color: shadowColor.opacity(0.16), radius: 32, x: 0, y: 18)
            .onAppear(perform: start)
            .accessibilityElement(children: .combine)
            .accessibilityLabel("Glass panel, device connected notification")
    }

    private var content: some View {
        HStack(spacing: 13) {
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .fill(.white.opacity(0.18))
                .frame(width: 40, height: 40)
                .overlay {
                    Image(systemName: "wifi")
                        .font(.system(size: 16, weight: .semibold))
                        .foregroundStyle(.white)
                }

            VStack(alignment: .leading, spacing: 3) {
                Text("Studio Display")
                    .font(.system(size: 15, weight: .semibold))
                    .foregroundStyle(.white)
                Text("Connected just now")
                    .font(.system(size: 12))
                    .foregroundStyle(.white.opacity(0.6))
            }
            Spacer(minLength: 0)
        }
    }

    /// A 90pt band travelling from off-card left to off-card right with a
    /// long dwell at the far end, masked to the card shape so nothing escapes
    /// the rounded corners.
    private var sheenOverlay: some View {
        GeometryReader { proxy in
            let w = proxy.size.width
            let h = proxy.size.height
            let x = -0.45 * w + (1.9 * w) * sweepPosition(current)
            Rectangle()
                .fill(
                    LinearGradient(
                        colors: [.clear, sheenColor.opacity(0.55), .clear],
                        startPoint: .leading,
                        endPoint: .trailing
                    )
                )
                .frame(width: 90, height: h * 2.4)
                .rotationEffect(.degrees(22))
                .position(x: x, y: h / 2)
        }
        .mask(shape)
        .blendMode(.plusLighter)
        .allowsHitTesting(false)
    }

    /// Travel over the first 60% of the loop, eased; dwell at the far (fully
    /// off-card) end for the remaining 40%. Both endpoints sit outside the
    /// card, so the wrap from 1 back to 0 is invisible.
    private func sweepPosition(_ p: Double) -> Double {
        let travelFraction = 0.6
        guard p < travelFraction else { return 1 }
        let x = p / travelFraction
        return 0.5 - 0.5 * cos(.pi * x)
    }

    private func start() {
        guard phase == nil, !reduceMotion, period > 0 else { return }
        withAnimation(.linear(duration: period).repeatForever(autoreverses: false)) {
            animated = 1
        }
    }
}

#Preview {
    DepthGlassSheen()
        .frame(width: 380, height: 240)
        .background(
            LinearGradient(colors: [.purple, .blue, .teal], startPoint: .topLeading, endPoint: .bottomTrailing)
        )
}
iOS 17 · No dependencies

SwiftUI note. SwiftUI's Material has no public blur radius, so blurRadius here shapes the ambient bloom bleeding through the glass rather than the true sampling blur — raise it for a thicker, softer pane. It loops continuously to stay demoable; for a real layout, a sheen that fires once via .onAppear and stops is often the tasteful default. On iOS 26, .glassEffect(.regular, in: shape) is worth swapping in for the material.

Dependencies

SwiftUI
No external dependencies

Requires iOS 17

Flutter
No external dependencies

Requires Flutter 3.27