SwiftUI.cc
Snippets
Lists & GridsHierarchical DataIntermediate

SwiftUI OutlineGroup and Hierarchical Lists

OutlineGroup and List(children:) render tree-shaped data with disclosure triangles — file browsers, org charts, and nested categories without manual recursion.

4 min readUpdated 2026-06
Project file tree
import SwiftUI

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

struct FileTree: View {
    let root = [
        FileItem(name: "Sources", children: [
            FileItem(name: "App.swift"),
            FileItem(name: "Views", children: [
                FileItem(name: "Home.swift")
            ])
        ]),
        FileItem(name: "README.md")
    ]

    var body: some View {
        List(root, children: \.children) { item in
            Label(item.name,
                  systemImage: item.children == nil ? "doc" : "folder")
        }
    }
}

OutlineGroup preview

Recursion as a key path

Tree UIs used to mean recursive view structs. OutlineGroup collapses that to one declaration: give it the roots and a key path to optional children, and it walks the structure, indenting and adding disclosure triangles as it goes.

OutlineGroup(root, children: \.children) { item in
    Text(item.name)
}

List(root, children: \.children) is the same engine with list styling — the form most apps want.

nil vs empty children

The optionality of children carries meaning. nil means "leaf — no triangle." An empty array means "branch that could have children" and still shows the disclosure control. Map your domain accordingly: files get nil, empty folders get [].

Controlling expansion

OutlineGroup keeps expansion internal. When requirements include expand-all, restore-on-launch, or analytics per toggle, compose the tree yourself from DisclosureGroup(isExpanded:) nodes — the visual result is identical and the state is yours.

Common mistakes

  • Empty-array leaves producing pointless triangles everywhere.
  • Unstable ids in tree nodes breaking expansion when data reloads.
  • Mixing drill-in NavigationLinks and inline expansion in one list, which disorients users.

Related reference