ai/ios-skills/ios-axiom-swiftui-containers-ref/SKILL.md
Reference — SwiftUI stacks, grids, outlines, and scroll enhancements through iOS 26
npx skillsauth add kurko/dotfiles axiom-swiftui-containers-refInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Stacks, grids, outlines, and scroll enhancements. iOS 14 through iOS 26.
Sources: WWDC 2020-10031, 2022-10056, 2023-10148, 2024-10144, 2025-256
| 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+ |
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
HStack {
Text("Left")
Spacer()
Text("Right")
}
Spacer(minLength: 20) // Minimum size
Render children only when visible. Use inside ScrollView.
ScrollView {
LazyVStack(spacing: 0) {
ForEach(items) { item in
ItemRow(item: item)
}
}
}
ScrollView {
LazyVStack(pinnedViews: [.sectionHeaders]) {
ForEach(sections) { section in
Section(header: SectionHeader(section)) {
ForEach(section.items) { item in
ItemRow(item: item)
}
}
}
}
}
Non-lazy grid with precise alignment. Loads all views at once.
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 columnsgridColumnAlignment(_:) — Override column alignmentGrid {
GridRow {
Text("Header").gridCellColumns(2)
}
GridRow {
Text("Left")
Text("Right").gridColumnAlignment(.trailing)
}
}
Vertical-scrolling grid. Define columns; rows grow unbounded.
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
ScrollView {
LazyVGrid(columns: columns, spacing: 16) {
ForEach(items) { item in
ItemCard(item: item)
}
}
}
Horizontal-scrolling grid. Define rows; columns grow unbounded.
let rows = [GridItem(.fixed(100)), GridItem(.fixed(100))]
ScrollView(.horizontal) {
LazyHGrid(rows: rows, spacing: 16) {
ForEach(items) { item in
ItemCard(item: item)
}
}
}
| Size | Behavior |
|------|----------|
| .fixed(CGFloat) | Exact width/height |
| .flexible(minimum:maximum:) | Fills space equally |
| .adaptive(minimum:maximum:) | Creates as many as fit |
// Adaptive: responsive column count
let columns = [GridItem(.adaptive(minimum: 150))]
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)
For custom hierarchical layouts outside List.
List {
ForEach(canvases) { canvas in
Section(header: Text(canvas.name)) {
OutlineGroup(canvas.graphics, children: \.children) { graphic in
GraphicRow(graphic: graphic)
}
}
}
}
@State private var isExpanded = false
DisclosureGroup("Advanced Options", isExpanded: $isExpanded) {
Toggle("Enable Feature", isOn: $feature)
Slider(value: $intensity)
}
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()
}
}
}
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 16) {
ForEach(items) { item in
CarouselCard(item: item).frame(width: 280)
}
}
.padding(.horizontal)
}
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)
| 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:
struct ContentView: View {
let columns = [GridItem(.adaptive(minimum: 150))] // ✅
var body: some View {
LazyVGrid(columns: columns) { ... }
}
}
ScrollView(.horizontal) {
LazyHStack {
ForEach(photoSets) { set in
ScrollView(.vertical) {
LazyVStack {
ForEach(set.photos) { PhotoView(photo: $0) }
}
}
}
}
}
Size views relative to scroll container.
ScrollView(.horizontal) {
LazyHStack {
ForEach(cards) { card in
CardView(card: card)
.containerRelativeFrame(.horizontal, count: 3, span: 1, spacing: 16)
}
}
}
Enable snapping.
ScrollView(.horizontal) {
LazyHStack {
ForEach(items) { ItemCard(item: $0) }
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
Track topmost visible item. Requires .id() on each item.
@State private var position: Item.ID?
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemRow(item: item).id(item.id)
}
}
}
.scrollPosition(id: $position)
.scrollTransition { content, phase in
content
.opacity(1 - abs(phase.value) * 0.5)
.scaleEffect(phase.isIdentity ? 1.0 : 0.75)
}
.onScrollGeometryChange(for: Bool.self) { geo in
geo.contentOffset.y < geo.contentInsets.top
} action: { _, isTop in
showBackButton = !isTop
}
VideoPlayer(player: player)
.onScrollVisibilityChange(threshold: 0.2) { visible in
visible ? player.play() : player.pause()
}
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
data-ai
Merge the current worktree branch into main and sync main back. Use when the user says "merge to main", "ship it", "merge and continue", or after completing a task in a worktree and wanting to continue with the next one.
tools
Synchronize AI agent skills, commands, configs, permissions, hooks, and instructions across Claude Code, Codex CLI, and other Agent Skills-compatible tools. Use when the user asks to pull skills from Claude into Codex, sync Codex work back to Claude, migrate agent commands, reconcile frontmatter, update permissions, or keep agent setup files in parity.
testing
Write or update UI-independent use cases for QA. Use when the user says "write use cases", "add use cases", "QA use cases", "update use cases", "compose use cases", or when starting implementation of a new feature (after plan approval). Also activates for "what should we test", "regression cases", or "use cases for QA".
documentation
Skill on how to write a task. Use when user asks you to write a task (for Asana, Linear, Jira, Notion and equivalent). Also activates when user says "create task", "write task", or similar task creation workflow requests.