Scroll Edge Header
A scroll view whose content dissolves under a custom header using the scroll edge effect.

- Modern
- iOS 26.0+
- beginner
- Origin
- Original work
- Found by
- scroll edge · scroll edge effect · header · sticky header · safe area bar · blur · scroll view · borde de desplazamiento · cabecera · cabecera fija · barra de área segura · desenfoque · desplazamiento
Description
Section titled “Description”A scroll view whose content dissolves under a custom header.
The header sits in a safe area bar, which is what lets the scroll edge effect reach it.
@available(iOS 26, *)struct ScrollEdgeHeaderDemo: View { @State private var isHard = false
private let colors: [Color] = [.pink, .orange, .yellow, .green, .teal, .blue, .indigo, .purple]
var body: some View { ScrollEdgeHeader(style: isHard ? .hard : .soft) { HStack { VStack(alignment: .leading, spacing: 0) { Text("Today") .font(.largeTitle.bold())
Text(isHard ? "Hard edge" : "Soft edge") .font(.subheadline.weight(.medium)) }
Spacer()
Button("Change edge style", systemImage: "circle.lefthalf.filled") { isHard.toggle() } .labelStyle(.iconOnly) .buttonStyle(.glass) .buttonBorderShape(.circle) // The regular size of an icon in a circle is too small to hit. .controlSize(.large) } .padding(.horizontal, 20) .padding(.bottom, 10) } content: { LazyVStack(spacing: 14) { ForEach(0..<30, id: \.self) { index in Label("Story \(index + 1)", systemImage: "sparkles") .font(.title3.bold()) .foregroundStyle(.white) .frame(maxWidth: .infinity, minHeight: 120, alignment: .bottomLeading) .padding(18) .background(colors[index % colors.count].gradient, in: .rect(cornerRadius: 24)) } } .padding(.horizontal, 20) } }}
@available(iOS 26, *)API reference
Section titled “API reference”| Parameter | Type | Default |
|---|---|---|
style | ScrollEdgeEffectStyle | .soft |
header | () -> Header | Required |
content | () -> Content | Required |
Copy this code into your project, or download the file.
// SPDX-License-Identifier: MIT// Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors
import SwiftUI
/// A scroll view whose content dissolves under a custom header.////// The header sits in a safe area bar, which is what lets the scroll edge effect reach it.@available(iOS 26, *)struct ScrollEdgeHeader<Header: View, Content: View>: View { var style: ScrollEdgeEffectStyle = .soft @ViewBuilder let header: () -> Header @ViewBuilder let content: () -> Content
var body: some View { ScrollView(content: content) .scrollEdgeEffectStyle(style, for: .top) .safeAreaBar(edge: .top, content: header) }}
// MARK: - Demo
@available(iOS 26, *)struct ScrollEdgeHeaderDemo: View { @State private var isHard = false
private let colors: [Color] = [.pink, .orange, .yellow, .green, .teal, .blue, .indigo, .purple]
var body: some View { ScrollEdgeHeader(style: isHard ? .hard : .soft) { HStack { VStack(alignment: .leading, spacing: 0) { Text("Today") .font(.largeTitle.bold())
Text(isHard ? "Hard edge" : "Soft edge") .font(.subheadline.weight(.medium)) }
Spacer()
Button("Change edge style", systemImage: "circle.lefthalf.filled") { isHard.toggle() } .labelStyle(.iconOnly) .buttonStyle(.glass) .buttonBorderShape(.circle) // The regular size of an icon in a circle is too small to hit. .controlSize(.large) } .padding(.horizontal, 20) .padding(.bottom, 10) } content: { LazyVStack(spacing: 14) { ForEach(0..<30, id: \.self) { index in Label("Story \(index + 1)", systemImage: "sparkles") .font(.title3.bold()) .foregroundStyle(.white) .frame(maxWidth: .infinity, minHeight: 120, alignment: .bottomLeading) .padding(18) .background(colors[index % colors.count].gradient, in: .rect(cornerRadius: 24)) } } .padding(.horizontal, 20) } }}
@available(iOS 26, *)#Preview { ScrollEdgeHeaderDemo()}