Surface materials
A surface style decides what the chrome paints behind compact and expanded nook content: a flat opaque panel, a frosted vibrancy material, or Apple’s Liquid Glass. It is a user-facing preference - the framework ships a Settings control for it and persists the choice - so most hosts never set it in code; they read the resolved value back to pick a matching palette.
Where the style is picked
Section titled “Where the style is picked”The style lives on NookAppearancePreferences.surfaceStyle:
public enum NookSurfaceStyle: String, Codable, Sendable, CaseIterable { case solid // opaque panel, matches the menu-bar notch (the default) case translucent // frosted vibrancy material, shows the wallpaper case liquidGlass // Apple's Liquid Glass material (macOS 26+)}surfaceStyle defaults to .solid. The framework owns the Settings panel that
writes it - the Appearance screen renders a segmented “Surface” picker (Solid /
Translucent / Liquid Glass) and persists the selection through
AppState.replaceAppearancePreferences(_:). Your code reads from it; see how a
host reads the style below.
To ship a non-default out of the box, seed it on
NookConfiguration.preferenceDefaults (a seed the user can always override in
Settings, never persisted on its own):
configuration.preferenceDefaults = NookPreferenceDefaults( appearance: NookAppearancePreferences(surfaceStyle: .liquidGlass))The three styles
Section titled “The three styles”The mapping from a style to a concrete NookBackdrop lives in
NookBackdropMapping.notchBackdrop, keyed by the style, the effective color
scheme, and whether Reduce Transparency is on.
.solidpaints a flat opaque fill - true black on dark chrome, true white on light - so the expanded panel reads as one continuous surface with the physical notch. NoNSVisualEffectViewis involved. This is the default and the most legible over any wallpaper..translucentpaints a frostedNSVisualEffectViewsidebar material sampling the wallpaper behind the window, with a legibility darken pass composited on top. The darken scales withbackdropStrength(the Settings “Translucency strength” slider) so the user can let more wallpaper through..liquidGlasspaints Apple’s Liquid Glass material on macOS 26 (Tahoe) and later, and a layered approximation on earlier systems. The default mapping uses neutral, untinted glass - the real material refracts the wallpaper on its own - with a light top-to-bottom darken so the surface reads glassier as it nears the wallpaper.
Liquid Glass in depth
Section titled “Liquid Glass in depth”Liquid Glass renders through NookBackdrop.liquidGlass(LiquidGlass). The
LiquidGlass spec carries four knobs that drive both render paths:
public struct LiquidGlass: Equatable, Sendable { public var tint: Color? // nil = neutral, clear glass (the default) public var tintStrength: CGFloat // 0...1, default 0.18; ignored when tint is nil public var highlightStrength: CGFloat // 0...1 specular rim + sheen, default 0.6 public var shading: Shading? // legibility gradient the caller fully owns}shading is a Gradient plus a direction (startPoint/endPoint, defaulting
top-to-bottom), so the legibility pass is entirely the caller’s - the surface
renders exactly what the spec carries and never substitutes its own. The default
framework mapping supplies a sensible top-to-bottom darken; a host returning its
own .liquidGlass from a backdrop resolver can replace it wholesale.
Real material vs the pre-Tahoe approximation
Section titled “Real material vs the pre-Tahoe approximation”The surface dispatches on availability. On macOS 26+ it draws the real system material; on macOS 15-25 it draws a layered approximation that reads as glass:
- Real material (macOS 26+). A
Color.clearpainted with.glassEffect(_:in:)using aGlassmaterial, clipped to the sameNookShapethe chrome already uses, with the host’sshadingoverlaid on top. A non-niltintbecomesGlass.regular.tint(tint.opacity(tintStrength)). macOS supplies its own edge highlights here, sohighlightStrengthonly adds a faint extra rim. - Approximation (macOS 15-25). A glassy
NSVisualEffectView(.hudWindow) material, an optional tint overlay, the sameshading, then the specular treatment that actually sells the glass read: a top-down sheen and a bright rim traced alongNookShape.highlightStrengthscales that sheen and rim.
Runtime gate and compile gate
Section titled “Runtime gate and compile gate”The real path is gated twice, and it matters for what you can build and where it runs:
- Runtime gate -
@available(macOS 26.0, *): the real.glassEffectmaterial only renders on macOS 26 and later. On an older OS the surface falls back to the approximation at runtime, so it cannot crash on systems without the material. - Compile gate -
#if compiler(>=6.2):Glassand.glassEffectexist only in the macOS 26 SDK (Xcode 26+, Swift 6.2). An@availablecheck still needs those symbols present in the SDK being compiled against, so an older Xcode cannot build the real path at all. The compile gate routes an earlier toolchain to the approximation unconditionally, so the package builds on Xcode before 26 instead of failing with “cannot find ‘Glass’ in scope”.
Putting both together: the package always builds, on any supported Xcode. The
real material needs the macOS 26 SDK (Xcode 26+) to build and macOS 26 at
runtime to render. Anywhere else, .liquidGlass still works - it just draws the
approximation.
Reduce Transparency
Section titled “Reduce Transparency”When the user enables Reduce Transparency, the mapping collapses both
translucent styles toward solid. .translucent and .liquidGlass both fall
back to a flat opaque fill (black on dark, white on light) - neither the frosted
material nor the glass renders when the user has opted out of translucency. The
guard is a single check in NookBackdropMapping.notchBackdrop:
if preferences.surfaceStyle == .solid || reduceTransparency { return .solid(isDark ? .black : .white)}So a host palette never has to special-case Reduce Transparency for the surface
material itself - the surface is already solid. (You may still want to nudge
subtleFill for contrast on a true-solid panel; see the Theming
guide.)
Read the style to pick a palette
Section titled “Read the style to pick a palette”A host that brands its chrome usually reads the chosen surface style and returns
a matching NookResolvedTheme. Branch on
appState.appearancePreferences.surfaceStyle:
configuration.theme = { appState in switch appState.appearancePreferences.surfaceStyle { case .solid: return SolidPalette.resolve(appState) case .translucent: return FrostPalette.resolve(appState) case .liquidGlass: return GlassPalette.resolve(appState) }}The switch is exhaustive over all three cases, so adding a palette branch for
.liquidGlass keeps it compiling. A glass-tuned palette typically leans on
lighter fills and brighter labels, since the glass keeps its own contrast and
needs less darken under chrome content than a frosted material does.
To paint brand-tinted glass instead of reading it back, supply a LiquidGlass
spec from a NookChromeBehavior backdrop resolver - that closure, not the
Settings style, is where the deeper customization lives:
configuration.chromeBehavior = NookChromeBehavior( backdrop: { preferences, scheme, reduceTransparency in .liquidGlass(.init(tint: .blue, tintStrength: 0.22)) })See also
Section titled “See also”- Theming - the palette resolver and
NookResolvedThemeslots that pair with the surface material. Sources/NookSurface/NookBackdrop.swift- theNookBackdropandLiquidGlasstypes, the source of truth for the knobs.Sources/NookKit/App/NookBackdropMapping.swift- how a style, color scheme, and Reduce Transparency map to a backdrop.