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.
Three layers
Section titled “Three layers”Expanded layout stacks three responsibilities:
NookSurface(NookView) - clips the notch shape, appliesNookStyle.expandedContentInsetsas.safeAreaInsetstrips, adds structural horizontal padding (topCornerRadius) for the panel ears, and derives the outernookContentInsetsfrom corner radii.NookKit(NookExpandedView) - pins the inner VStack toexpandedWidth, wraps it inmetrics.edgePadding, and re-injects reducednookContentInsetsfor descendants.- Host views - read
@Environment(\.nookContentInsets)when content pins to an edge or corner; useframe(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 │ │ │ ││ │ │ └────────────────────────────────────────────────────────────┘ │ │ ││ │ └────────────────────────────────────────────────────────────────┘ │ ││ └──────────────────────────────────────────────────────────────────────┘ │└──────────────────────────────────────────────────────────────────────────┘Knob reference
Section titled “Knob reference”| Knob | Layer | What it does |
|---|---|---|
NookConfiguration.expandedWidth | NookKit | Fixed width of the inner VStack (default 520). Does not resize the glass by itself - the surface measures the wrapped chrome. |
NookChromeMetrics.edgePadding | NookKit | Padding around the inner VStack inside the expanded surface. Default 8 (NookLayout.edgePadding). Subtracted before re-injecting nookContentInsets. |
NookStyle.expandedContentInsets | NookSurface | Per-edge .safeAreaInset strip the chrome reserves around expanded content. Default: top 0, other edges 8. |
NookStyle.topCornerRadius / bottomCornerRadius | NookSurface | Panel corner radii; drive clip shape and nookContentInsets derivation. |
EnvironmentValues.nookContentInsets | Surface -> host | Residual 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.
Usable content width (worked example)
Section titled “Usable content width (worked example)”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):
| Edge | Residual |
|---|---|
| top | 19 |
| bottom | 16 |
| leading | 16 |
| trailing | 16 |
Step 2 - inner nookContentInsets (after NookExpandedView subtracts
edgePadding):
| Edge | Residual |
|---|---|
| top | 11 |
| bottom | 8 |
| leading | 8 |
| trailing | 8 |
Step 3 - edge-aligned text width inside the 520 pt VStack:
520 − leading(8) − trailing(8) = 504 ptSettingsView 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.bottomrises to 22 (24 − 2) - bottom-corner content must inset more; horizontally-centered rows are unaffected and sit ~6 pt closer to the rounded bottom.
Recommended host patterns
Section titled “Recommended host patterns”Set width and trim chrome insets in configuration
Section titled “Set width and trim chrome insets in configuration”var configuration = NookConfiguration()configuration.expandedWidth = 600configuration.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.
Read nookContentInsets in the home view
Section titled “Read nookContentInsets in the home view”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.
Full-width bottom command rows
Section titled “Full-width bottom command rows”Bottom toolbars that should span the content column need two things:
frame(maxWidth: .infinity, alignment: .leading)on the row - otherwise SwiftUI shrink-wraps theHStackto its buttons and the row looks narrower than the panel.- 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.
When you can skip nookContentInsets
Section titled “When you can skip nookContentInsets”- 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.
Anti-pattern: double horizontal padding
Section titled “Anti-pattern: double horizontal padding”// 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.
See also
Section titled “See also”Examples/LayoutNook/main.swift- working 600 pt host with trimmed bottom inset and a full-width command row.- Theming - corner radii and
expandedContentInsetsin 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.