SwiftUI ContentBuilder: one builder name for different content


Greetings, traveler!

SwiftUI now has a new ContentBuilder attribute. The declaration is small:

typealias ContentBuilder = ViewBuilder

So this is not a completely separate result builder. Apple documents ContentBuilder as a type alias for ViewBuilder.

But the name is the interesting part. ViewBuilder always sounded tied to views. SwiftUI has grown beyond that. We now build views, toolbar content, commands, tabs, keyframe tracks, compositor content, and other framework-specific content types.

Why the new name matters

Before this API, SwiftUI had several specialized builders:

@ViewBuilder
@ToolbarContentBuilder
@CommandsBuilder
@TabContentBuilder
@KeyframeTrackContentBuilder
@CompositorContentBuilder

These names are precise, but they also expose a lot of framework detail at the call site. A toolbar needs toolbar content. A tab view needs tab content. A keyframe animation needs keyframe tracks. Each area had its own builder name.

Apple now describes ContentBuilder as a unified replacement for type-specific builders like ToolbarContentBuilder and CommandsBuilder.

That does not mean every old builder name disappears from SwiftUI documentation overnight. But for new APIs and new code, ContentBuilder gives SwiftUI a broader spelling for “this closure builds SwiftUI content”.

Basic view example

For ordinary view content, the code looks familiar:

@ContentBuilder
private func header() -> some View {
    Text("Library")
        .font(.title)

    Text("Recently updated")
        .foregroundStyle(.secondary)
}

This is the same kind of code where many of us would previously write @ViewBuilder.

A custom container can also use it:

struct Card<Content: View>: View {
    private let content: Content

    init(@ContentBuilder content: () -> Content) {
        self.content = content()
    }

    var body: some View {
        VStack(alignment: .leading) {
            content
        }
        .padding()
    }
}

The return type still matters. If the closure produces some View, the final result must be valid view content.

Toolbar content example

The more interesting case is content that is not just a view hierarchy.

Toolbar APIs have traditionally used ToolbarContentBuilder. With ContentBuilder, the spelling can become more general:

@ContentBuilder
private var editorToolbar: some ToolbarContent {
    ToolbarItem {
        Button("Undo") {}
    }

    ToolbarItem {
        Button("Redo") {}
    }
}

This is the part that makes the new name useful. The builder attribute no longer has to say “toolbar” at the declaration site. The result type already says that:

some ToolbarContent

So the context still controls what the closure is allowed to produce.

What this does not change

ContentBuilder does not make SwiftUI content interchangeable.

A ToolbarItem does not become a normal View just because the closure uses @ContentBuilder. A tab declaration does not become toolbar content. The surrounding return type or parameter type still defines the expected result.

That is the safety boundary:

@ContentBuilder
private var toolbar: some ToolbarContent {
    ToolbarItem {
        Button("Save") {}
    }
}

The builder helps construct the content. It does not erase the difference between View, ToolbarContent, TabContent, Commands, or other SwiftUI content protocols.

Availability and migration

Apple lists ContentBuilder with availability back to iOS 13, iPadOS 13, macOS 10.15, tvOS 13, visionOS 1, and watchOS 6.

For new code on the new SDK, I would start using ContentBuilder in places where the API is really about generic SwiftUI content:

init(@ContentBuilder content: () -> Content)

It reads better than @ViewBuilder when the content is not necessarily limited to views.

Why this matters for the compiler

Apple describes ContentBuilder as a type alias for ViewBuilder, but the documentation also notes an important implementation detail: its build functions do not enforce protocol conformance directly. Type safety is preserved through conditional conformances on the resulting content types.

That matters because SwiftUI code often gives the type checker a lot of contextual work: result builders, overloaded APIs, generic content types, and inferred return types all meet in the same expression. ContentBuilder does not magically solve every type-checking problem, but it fits the same direction as recent Swift compiler work: reduce unnecessary ambiguity and make constraint solving less expensive in common SwiftUI code.

Final thought

ContentBuilder is a small API, but it points to a real cleanup in SwiftUI. The framework now has many kinds of declarative content. Views are only one of them. ToolbarContentBuilder, TabContentBuilder, CommandsBuilder, and similar types made that visible, but they also made the builder surface fragmented.

ContentBuilder gives SwiftUI one general word for these closures. The result type still keeps the rules strict, so View, ToolbarContent, TabContent, and other content types do not become interchangeable.

The nice part is that this is not only a naming cleanup. Type safety is preserved through conditional conformances on the resulting builder types. That makes the API feel broader without making it loose.