Skip to content
GitHub

Layout and content insets

The expanded panel’s glass width and the content column inside it are controlled by different knobs on different layers. Stacking horizontal padding - for example .padding(.horizontal, 12) on a home view when the framework already applies metrics.edgePadding - is the most common cause of dead space beside answer text and bottom command rows.

This guide maps each knob, shows how they compose, and points at Examples/LayoutNook/main.swift for a working host pattern.

Expanded layout stacks three responsibilities:

  1. NookSurface (NookView) - clips the notch shape, applies NookStyle.expandedContentInsets as .safeAreaInset strips, adds structural horizontal padding (topCornerRadius) for the panel ears, and derives the outer nookContentInsets from corner radii.
  2. NookKit (NookExpandedView) - pins the inner VStack to expandedWidth, wraps it in metrics.edgePadding, and re-injects reduced nookContentInsets for descendants.
  3. Host views - read @Environment(\.nookContentInsets) when content pins to an edge or corner; use frame(maxWidth: .infinity, …) when a row should span the full content column.
┌─ NookShape clip (topCornerRadius / bottomCornerRadius) ─────────────────┐
│ .padding(.horizontal, topCornerRadius) ← structural ear clearance │
│ ┌─ expandedContent HStack ────────────────────────────────────────────┐ │
│ │ safeAreaInset: expandedContentInsets (default 0/8/8/8) │ │
│ │ ┌─ NookExpandedView ─────────────────────────────────────────────┐ │ │
│ │ │ .padding(edgePadding) ← default 8 pt │ │ │
│ │ │ ┌─ VStack .frame(width: expandedWidth ?? 520) ──────────────┐ │ │ │
│ │ │ │ env: nookContentInsets (reduced by edgePadding) │ │ │ │
│ │ │ │ → top bar, Settings, host home │ │ │ │
│ │ │ └────────────────────────────────────────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
KnobLayerWhat it does
NookConfiguration.expandedWidthNookKitFixed width of the inner VStack (default 520). Does not resize the glass by itself - the surface measures the wrapped chrome.
NookChromeMetrics.edgePaddingNookKitPadding around the inner VStack inside the expanded surface. Default 8 (NookLayout.edgePadding). Subtracted before re-injecting nookContentInsets.
NookStyle.expandedContentInsetsNookSurfacePer-edge .safeAreaInset strip the chrome reserves around expanded content. Default: top 0, other edges 8.
NookStyle.topCornerRadius / bottomCornerRadiusNookSurfacePanel corner radii; drive clip shape and nookContentInsets derivation.
EnvironmentValues.nookContentInsetsSurface -> hostResidual curve clearance relative to the host content frame. Use for edge/corner-pinned layout - not as a substitute for centering.

expandedContentInsets and edgePadding both default to 8 pt on the sides for historical parity, but they are independent knobs on different layers.

At the framework defaults - expandedWidth = 520, radii top 19 / bottom 24, expandedContentInsets (0, 8, 8, 8), edgePadding = 8:

Step 1 - outer nookContentInsets (injected by NookView):

EdgeResidual
top19
bottom16
leading16
trailing16

Step 2 - inner nookContentInsets (after NookExpandedView subtracts edgePadding):

EdgeResidual
top11
bottom8
leading8
trailing8

Step 3 - edge-aligned text width inside the 520 pt VStack:

520 − leading(8) − trailing(8) = 504 pt

SettingsView and NookTopBar follow this pattern - section labels and icon clusters pad by contentInsets.leading / contentInsets.trailing so everything shares one left margin.

Wider panel (600 pt, trimmed bottom inset)

Section titled “Wider panel (600 pt, trimmed bottom inset)”

LayoutNook sets expandedWidth = 600 and tightens only the bottom chrome strip (expandedContentInsets.bottom = 2, sides unchanged):

  • Inner VStack width: 600 pt
  • Edge-aligned usable width: 600 − 8 − 8 = 584 pt
  • nookContentInsets.bottom rises to 22 (24 − 2) - bottom-corner content must inset more; horizontally-centered rows are unaffected and sit ~6 pt closer to the rounded bottom.

Set width and trim chrome insets in configuration

Section titled “Set width and trim chrome insets in configuration”
var configuration = NookConfiguration()
configuration.expandedWidth = 600
configuration.style = NookStyle(
topCornerRadius: 19,
bottomCornerRadius: 24,
expandedContentInsets: NookEdgeInsets(top: 0, bottom: 2, leading: 8, trailing: 8)
)
configuration.setHome { MyHomeView() }
NookApp.main(configuration)

Leave metrics.edgePadding at the default unless you have a deliberate reason to change the wrapper padding - most hosts only need expandedWidth and expandedContentInsets.

struct MyHomeView: View {
@Environment(\.nookContentInsets) private var contentInsets
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text("Answer text …")
.padding(.leading, contentInsets.leading)
.padding(.trailing, contentInsets.trailing)
Spacer(minLength: 0)
commandRow
}
}
private var commandRow: some View {
HStack { /* … */ }
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, contentInsets.leading)
.padding(.trailing, contentInsets.trailing)
.padding(.bottom, contentInsets.bottom)
}
}

Do not add .padding(.horizontal, 12) (or any fixed horizontal padding) on the home root - NookExpandedView already applies metrics.edgePadding. Extra padding narrows the effective column and leaves visible dead space beside text and toolbars.

Bottom toolbars that should span the content column need two things:

  1. frame(maxWidth: .infinity, alignment: .leading) on the row - otherwise SwiftUI shrink-wraps the HStack to its buttons and the row looks narrower than the panel.
  2. Edge insets from nookContentInsets - not a second horizontal padding pass.

Centered command rows (icons in the middle of the panel) can ignore horizontal nookContentInsets; only corner-pinned content needs the leading/trailing values.

  • Centered placeholder content (title + subtitle in the middle) - the default demo home does this with vertical padding only.
  • Full-bleed custom art that intentionally reaches toward the curves - rare; test on a notched display.

When content aligns with Settings rows or the top-bar leading cluster, always use nookContentInsets.

// Avoid — stacks on top of NookExpandedView's edgePadding (8) and any
// expandedContentInsets the chrome already consumed.
VStack { }
.padding(.horizontal, 12)

If the panel feels narrower than the glass, check for fixed horizontal padding on the registered home view before tuning expandedWidth.

  • Examples/LayoutNook/main.swift - working 600 pt host with trimmed bottom inset and a full-width command row.
  • Theming - corner radii and expandedContentInsets in context.
  • Settings chrome - top-bar layout that shares the same inset contract.
  • Sources/NookKit/App/Views/Settings/SettingsView.swift - canonical edge-aligned list layout inside the framework.
  • Tests/NookKitTests/NookContentInsetsTests.swift - executable spec for inset derivation math.