# Layout and content insets

> How expanded width, chrome insets, and nookContentInsets compose into usable content width.

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

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                            │ │ │ │
│  │  │  └────────────────────────────────────────────────────────────┘ │ │ │
│  │  └────────────────────────────────────────────────────────────────┘ │ │
│  └──────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
```

## 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)

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 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)

`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.

## Recommended host patterns

### Set width and trim chrome insets in configuration

```swift
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`.

### Read `nookContentInsets` in the home view

```swift
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

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.

### 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

```swift
// 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

- `Examples/LayoutNook/main.swift` - working 600 pt host with trimmed bottom inset
  and a full-width command row.
- [Theming](/guides/theming/#chrome-shape-animation-and-width) - corner radii and
  `expandedContentInsets` in context.
- [Settings chrome](/guides/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.
