home / skills / charleswiltgen / axiom / axiom-swiftui-containers-ref

This skill helps you master SwiftUI containers and scroll enhancements across stacks, grids, outlines, and lists with practical usage patterns.

npx playbooks add skill charleswiltgen/axiom --skill axiom-swiftui-containers-ref

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
8.3 KB
---
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

Overview

This skill is a compact reference for SwiftUI container patterns through iOS 26, covering stacks, grids, outlines, and modern scroll enhancements. It distills practical APIs, performance guidance, and code fragments for building responsive layouts on xOS. Use it to choose the right container and optimize scrolling and hierarchy rendering.

How this skill works

The reference catalogs container types (VStack/HStack/ZStack, Lazy stacks, Grid, LazyVGrid/LazyHGrid, List/OutlineGroup, DisclosureGroup) and maps them to typical use cases and iOS availability. It highlights key modifiers and lifecycle behaviors (pinning, lazy loading, grid spanning) and explains scroll innovations introduced in iOS 17–18 such as scrollTargetLayout, scrollPosition, scrollTransition, and onScrollGeometryChange. Performance guidance shows when to prefer lazy containers and how to cache GridItem arrays.

When to use it

  • Use VStack/HStack/ZStack for fixed small layouts and precise alignment (1–20 views).
  • Use LazyVStack/LazyHStack or List for large scrolling collections (20+ items).
  • Use LazyVGrid/LazyHGrid for large grid collections; use Grid for small, static, precisely aligned forms.
  • Use List(children:) or OutlineGroup for hierarchical data and file-browser style UIs.
  • Use scrollTargetLayout, scrollPosition, and scrollTransition to enable snapping, position tracking, and animated transitions in scroll views.

Best practices

  • Cache GridItem arrays and column/row definitions outside the view body to avoid recomputation.
  • Prefer lazy containers inside ScrollView to defer view creation and reduce memory/CPU for large lists.
  • Use .id() on ForEach items when using scrollPosition or position tracking features.
  • Pick Grid (non-lazy) only for small, static datasets where deterministic layout matters; otherwise prefer LazyVGrid/LazyHGrid.
  • Pin section headers with LazyVStack(pinnedViews:) for performant sticky headers. Implement onScrollGeometryChange sparingly for expensive work.

Example use cases

  • Photo gallery: LazyVGrid with adaptive GridItem(minimum:) for responsive columns and AsyncImage for thumbnails.
  • Horizontal carousel: ScrollView(.horizontal) + LazyHStack with .scrollTargetLayout() and .scrollTargetBehavior(.viewAligned) for snapping cards.
  • File browser: List(selection:) with OutlineGroup or List(files, children:) for native expand/collapse and sidebar behavior.
  • Large feed: ScrollView { LazyVStack { ForEach(items) } } taking advantage of iOS 26 list performance gains for 100k+ items.
  • Nested scrolling: horizontal ScrollView with LazyHStack containing vertical ScrollViews with LazyVStacks — lazy loading defers inner content until visible.

FAQ

When should I use Grid vs LazyVGrid?

Use Grid for small, static forms requiring exact alignment (loads all views). Use LazyVGrid for larger, scrollable, or dynamic collections to defer view creation.

How do I track the topmost visible item?

Give each item an .id() and use .scrollPosition(id: $state) to update a bound identifier for the topmost visible item.