---
name: axiom-swiftui-containers-ref
description: Reference — SwiftUI stacks, grids, outlines, and scroll enhancements through iOS 26
license: MIT
metadata:
  version: "1.2.0"
---

# SwiftUI Containers Reference

Stacks, grids, outlines, and scroll enhancements. iOS 14 through iOS 26.

**Sources**: WWDC 2020-10031, 2022-10056, 2023-10148, 2024-10144, 2025-256

## Quick Decision

| Use Case | Container | iOS |
|----------|-----------|-----|
| Fixed views vertical/horizontal | VStack / HStack | 13+ |
| Overlapping views | ZStack | 13+ |
| Large scrollable list | LazyVStack / LazyHStack | 14+ |
| Multi-column grid | LazyVGrid | 14+ |
| Multi-row grid (horizontal) | LazyHGrid | 14+ |
| Static grid, precise alignment | Grid | 16+ |
| Hierarchical data (tree) | List with `children:` | 14+ |
| Custom hierarchies | OutlineGroup | 14+ |
| Show/hide content | DisclosureGroup | 14+ |

---

## Part 1: Stacks

### VStack, HStack, ZStack

```swift
VStack(alignment: .leading, spacing: 12) {
    Text("Title")
    Text("Subtitle")
}

HStack(alignment: .top, spacing: 8) {
    Image(systemName: "star")
    Text("Rating")
}

ZStack(alignment: .bottomTrailing) {
    Image("photo")
    Badge()
}
```

**ZStack alignments**: `.center` (default), `.top`, `.bottom`, `.leading`, `.trailing`, `.topLeading`, `.topTrailing`, `.bottomLeading`, `.bottomTrailing`

### Spacer

```swift
HStack {
    Text("Left")
    Spacer()
    Text("Right")
}

Spacer(minLength: 20)  // Minimum size
```

---

### LazyVStack, LazyHStack (iOS 14+)

Render children only when visible. Use inside ScrollView.

```swift
ScrollView {
    LazyVStack(spacing: 0) {
        ForEach(items) { item in
            ItemRow(item: item)
        }
    }
}
```

### Pinned Section Headers

```swift
ScrollView {
    LazyVStack(pinnedViews: [.sectionHeaders]) {
        ForEach(sections) { section in
            Section(header: SectionHeader(section)) {
                ForEach(section.items) { item in
                    ItemRow(item: item)
                }
            }
        }
    }
}
```

---

## Part 2: Grids

### Grid (iOS 16+)

Non-lazy grid with precise alignment. Loads all views at once.

```swift
Grid(alignment: .leading, horizontalSpacing: 10, verticalSpacing: 10) {
    GridRow {
        Text("Name")
        TextField("Enter name", text: $name)
    }
    GridRow {
        Text("Email")
        TextField("Enter email", text: $email)
    }
}
```

**Modifiers**:
- `gridCellColumns(_:)` — Span multiple columns
- `gridColumnAlignment(_:)` — Override column alignment

```swift
Grid {
    GridRow {
        Text("Header").gridCellColumns(2)
    }
    GridRow {
        Text("Left")
        Text("Right").gridColumnAlignment(.trailing)
    }
}
```

---

### LazyVGrid (iOS 14+)

Vertical-scrolling grid. Define **columns**; rows grow unbounded.

```swift
let columns = [
    GridItem(.flexible()),
    GridItem(.flexible()),
    GridItem(.flexible())
]

ScrollView {
    LazyVGrid(columns: columns, spacing: 16) {
        ForEach(items) { item in
            ItemCard(item: item)
        }
    }
}
```

### LazyHGrid (iOS 14+)

Horizontal-scrolling grid. Define **rows**; columns grow unbounded.

```swift
let rows = [GridItem(.fixed(100)), GridItem(.fixed(100))]

ScrollView(.horizontal) {
    LazyHGrid(rows: rows, spacing: 16) {
        ForEach(items) { item in
            ItemCard(item: item)
        }
    }
}
```

### GridItem.Size

| Size | Behavior |
|------|----------|
| `.fixed(CGFloat)` | Exact width/height |
| `.flexible(minimum:maximum:)` | Fills space equally |
| `.adaptive(minimum:maximum:)` | Creates as many as fit |

```swift
// Adaptive: responsive column count
let columns = [GridItem(.adaptive(minimum: 150))]
```

---

## Part 3: Outlines

### List with Hierarchical Data (iOS 14+)

```swift
struct FileItem: Identifiable {
    let id = UUID()
    var name: String
    var children: [FileItem]?  // nil = leaf
}

List(files, children: \.children) { file in
    Label(file.name, systemImage: file.children != nil ? "folder" : "doc")
}
.listStyle(.sidebar)
```

### OutlineGroup (iOS 14+)

For custom hierarchical layouts outside List.

```swift
List {
    ForEach(canvases) { canvas in
        Section(header: Text(canvas.name)) {
            OutlineGroup(canvas.graphics, children: \.children) { graphic in
                GraphicRow(graphic: graphic)
            }
        }
    }
}
```

### DisclosureGroup (iOS 14+)

```swift
@State private var isExpanded = false

DisclosureGroup("Advanced Options", isExpanded: $isExpanded) {
    Toggle("Enable Feature", isOn: $feature)
    Slider(value: $intensity)
}
```

---

## Part 4: Common Patterns

### Photo Grid

```swift
let columns = [GridItem(.adaptive(minimum: 100), spacing: 2)]

ScrollView {
    LazyVGrid(columns: columns, spacing: 2) {
        ForEach(photos) { photo in
            AsyncImage(url: photo.thumbnailURL) { image in
                image.resizable().aspectRatio(1, contentMode: .fill)
            } placeholder: { Color.gray }
            .aspectRatio(1, contentMode: .fill)
            .clipped()
        }
    }
}
```

### Horizontal Carousel

```swift
ScrollView(.horizontal, showsIndicators: false) {
    LazyHStack(spacing: 16) {
        ForEach(items) { item in
            CarouselCard(item: item).frame(width: 280)
        }
    }
    .padding(.horizontal)
}
```

### File Browser

```swift
List(selection: $selection) {
    OutlineGroup(rootItems, children: \.children) { item in
        Label {
            Text(item.name)
        } icon: {
            Image(systemName: item.children != nil ? "folder.fill" : "doc.fill")
        }
    }
}
.listStyle(.sidebar)
```

---

## Part 5: Performance

### When to Use Lazy

| Size | Scrollable? | Use |
|------|-------------|-----|
| 1-20 | No | VStack/HStack |
| 1-20 | Yes | VStack/HStack in ScrollView |
| 20-100 | Yes | LazyVStack/LazyHStack |
| 100+ | Yes | LazyVStack/LazyHStack or List |
| Grid <50 | No | Grid |
| Grid 50+ | Yes | LazyVGrid/LazyHGrid |

**Cache GridItem arrays** — define outside body:

```swift
struct ContentView: View {
    let columns = [GridItem(.adaptive(minimum: 150))]  // ✅
    var body: some View {
        LazyVGrid(columns: columns) { ... }
    }
}
```

### iOS 26 Performance

- **6x faster list loading** for 100k+ items
- **16x faster list updates**
- **Reduced dropped frames** in scrolling
- **Nested ScrollViews with lazy stacks** now properly defer loading:

```swift
ScrollView(.horizontal) {
    LazyHStack {
        ForEach(photoSets) { set in
            ScrollView(.vertical) {
                LazyVStack {
                    ForEach(set.photos) { PhotoView(photo: $0) }
                }
            }
        }
    }
}
```

---

## Part 6: Scroll Enhancements

### containerRelativeFrame (iOS 17+)

Size views relative to scroll container.

```swift
ScrollView(.horizontal) {
    LazyHStack {
        ForEach(cards) { card in
            CardView(card: card)
                .containerRelativeFrame(.horizontal, count: 3, span: 1, spacing: 16)
        }
    }
}
```

### scrollTargetLayout (iOS 17+)

Enable snapping.

```swift
ScrollView(.horizontal) {
    LazyHStack {
        ForEach(items) { ItemCard(item: $0) }
    }
    .scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
```

### scrollPosition (iOS 17+)

Track topmost visible item. **Requires `.id()` on each item.**

```swift
@State private var position: Item.ID?

ScrollView {
    LazyVStack {
        ForEach(items) { item in
            ItemRow(item: item).id(item.id)
        }
    }
}
.scrollPosition(id: $position)
```

### scrollTransition (iOS 17+)

```swift
.scrollTransition { content, phase in
    content
        .opacity(1 - abs(phase.value) * 0.5)
        .scaleEffect(phase.isIdentity ? 1.0 : 0.75)
}
```

### onScrollGeometryChange (iOS 18+)

```swift
.onScrollGeometryChange(for: Bool.self) { geo in
    geo.contentOffset.y < geo.contentInsets.top
} action: { _, isTop in
    showBackButton = !isTop
}
```

### onScrollVisibilityChange (iOS 18+)

```swift
VideoPlayer(player: player)
    .onScrollVisibilityChange(threshold: 0.2) { visible in
        visible ? player.play() : player.pause()
    }
```

---

## Resources

**WWDC**: 2020-10031, 2022-10056, 2023-10148, 2024-10144, 2025-256

**Docs**: /swiftui/lazyvstack, /swiftui/lazyvgrid, /swiftui/lazyhgrid, /swiftui/grid, /swiftui/outlinegroup, /swiftui/disclosuregroup

**Skills**: axiom-swiftui-layout, axiom-swiftui-layout-ref, axiom-swiftui-nav, axiom-swiftui-26-ref
