Skip to content
GitHub

Rim glow and edge fade

Two opt-in effects for the panel itself. The rim glow lights the chrome’s edge in a color your content chooses - blue while work is running, say - so the user catches the state at the edge of vision. The scroll edge fade softens scrolling content where it meets the panel’s edges instead of cutting it off.

Both are off until you opt in, and both work in the notch, floating, and auto layouts. Examples/CompanionNook/main.swift uses both: its rim glows blue while the sleep timer runs, and its Up Next and Library lists fade at the edges.

The rim is two layers: a crisp line traced just inside the chrome’s edge, and a soft halo that spills past it and slowly breathes.

Publish a color from your content with nookRimGlow(_:); publish nil to turn it off:

struct SyncStatus: View {
@ObservedObject var sync: SyncModel
var body: some View {
SyncBadge(sync: sync)
.nookRimGlow(sync.isRunning ? .blue : nil)
}
}

This is the same seam as the ambient wash (nookAmbientColor(_:), backed by NookAmbientColorPreferenceKey): content publishes a color through a SwiftUI preference, NookRimGlowPreferenceKey, and the surface draws it without knowing why it was chosen. When several views publish, the last non-nil color wins.

One difference matters. The ambient wash is read from expanded content only; the rim is read from everything the chrome hosts - the compact slots, the expanded content, and every companion surface - because a state signal must survive the nook collapsing. Publish from both your compact and expanded content, or from a companion, whose content stays mounted in both states.

NookConfiguration.rimGlow sets the look; the defaults are shown:

configuration.rimGlow = NookRimGlowStyle(
lineWidth: 1.5, // the crisp line inside the edge
glowRadius: 8, // the halo's blur; 0 draws the line alone
intensity: 0.9, // 0...1 peak strength
pulses: true, // the halo breathes while lit
followsAmbientColor: false // see below
)

With followsAmbientColor: true, the rim takes the ambient wash color whenever no content publishes a rim color, so a host that tints its surface gets a matching edge. It is off by default, so a host already using the ambient wash never gains a rim it did not ask for.

The rim adapts to the user’s settings on its own:

Setting Rim
Reduce Motion The halo stays lit but stops breathing. Colors cross-fade without motion.
Increase Contrast No soft halo and no breathing; the line is drawn twice as heavy and fully opaque.
Reduce Transparency No translucent halo; the line stays.

In the notch layout the chrome’s top edge is fused with the menu bar and the hardware notch, so the rim fades out across that band and rises out of the menu bar instead of outlining it; the compact pill glows mostly along its bottom edge. The floating panel glows all the way around.

The rim is drawn by the surface for the chrome itself. Companion surfaces do not glow, but they can light the chrome’s rim.

Set the fade once on the configuration:

configuration.scrollEdgeFade = .standard // every edge, 20 pt deep

The framework’s own scroll views follow it - the built-in Settings screen and the NookComponents file shelf - and yours do too once you mark them with nookScrollEdgeFade(axes:):

ScrollView {
LazyVStack { rows }
}
.nookScrollEdgeFade(axes: .vertical)
ScrollView(.horizontal) {
HStack { chips }
}
.nookScrollEdgeFade(axes: .horizontal)

A scroll view only fades the edges along its own scroll axes, and does nothing while the panel’s fade is off - so the modifier is safe to leave in place.

To fade a single scroll view regardless of the panel setting, pass a fade directly:

ScrollView { rows }
.nookScrollEdgeFade(NookScrollEdgeFade(edges: .bottom, length: 28))

NookScrollEdgeFade has two fields: edges (default .all) and length, the depth of the fade in points (default 20).

  • macOS 26 and later, top and bottom edges - Apple’s soft scroll edge effect (scrollEdgeEffectStyle(.soft, for:)). The system draws it only where content scrolls under a bar, so the fade reserves a safeAreaBar of length on each faded edge.
  • macOS 26, leading and trailing edges, and every edge on macOS 15-25 - a gradient mask. The system effect does not exist on earlier systems and is not drawn for side edges.

Either way, length is reserved as a content margin on each faded edge, so content at rest is never faded - only content that scrolls into the margin is. Like the Liquid Glass backdrop, the system path is gated at compile time as well as at run time, so the package still builds with an Xcode older than 26.

  • Companion surfaces - floating your own views beside the nook, and a convenient place to light the rim from.
  • Surface materials - the backdrop the rim is drawn on.
  • Sources/NookSurface/NookRimGlow.swift and Sources/NookSurface/NookScrollEdgeFade.swift - the types behind this guide.