# Axiom Swiftui 26 Ref

> Use when implementing iOS 26 SwiftUI features - covers Liquid Glass design system, performance improvements, @Animatable macro, 3D spatial layout, scene bridging, WebView/WebPage, AttributedString rich text editing, drag and drop enhancements, and visionOS integration for iOS 26+

- Skill: `majiayu000/axiom-swiftui-26-ref-2` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add majiayu000/axiom-swiftui-26-ref-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/axiom-swiftui-26-ref-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- License: MIT
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/majiayu000/axiom-swiftui-26-ref-2

---


# SwiftUI 26 Features

## Overview

Comprehensive guide to new SwiftUI features in iOS 26, iPadOS 26, macOS Tahoe, watchOS 26, and visionOS 26. From the Liquid Glass design system to rich text editing, these enhancements make SwiftUI more powerful across all Apple platforms.

**Core principle** From low level performance improvements all the way up through the buttons in your user interface, there are some major improvements across the system.

## When to Use This Skill

- Adopting the Liquid Glass design system
- Implementing rich text editing with AttributedString
- Embedding web content with WebView
- Optimizing list and scrolling performance
- Using the @Animatable macro for custom animations
- Building 3D spatial layouts on visionOS
- Bridging SwiftUI scenes to UIKit/AppKit apps
- Implementing drag and drop with multiple items
- Creating 3D charts with Chart3D
- Adding widgets to visionOS or CarPlay
- Adding custom tick marks to sliders (chapter markers, value indicators)
- Constraining slider selection ranges with `enabledBounds`
- Customizing slider appearance (thumb visibility, current value labels)
- Creating sticky safe area bars with blur effects
- Opening URLs in in-app browser
- Using system-styled close and confirm buttons
- Applying glass button styles (iOS 26.1+)
- Controlling button sizing behavior
- Implementing compact search toolbars

## System Requirements

#### iOS 26+, iPadOS 26+, macOS Tahoe+, watchOS 26+, visionOS 26+

---

## Liquid Glass Design System

#### For comprehensive Liquid Glass coverage, see
- `axiom-liquid-glass` skill — Design principles, implementation, variants, design review pressure
- `axiom-liquid-glass-ref` skill — App-wide adoption guide (app icons, controls, navigation, menus, windows)

### Overview

The new design system provides "a bright and fluid experience that's consistent across Apple platforms." Apps automatically adopt the new appearance upon recompilation - navigation containers, tab bars, and toolbars update automatically.

#### Key visual elements
- Glassy sidebars on iPad/macOS that reflect surrounding content
- Compact tab bars on iPhone
- Liquid Glass toolbar items with morphing transitions
- Blur effects on scroll edges

### Automatic Adoption

```swift
// No code changes required - recompile and get new design
NavigationSplitView {
    List {
        // Sidebar automatically gets glassy appearance on iPad/macOS
    }
} detail: {
    // Detail view
}

// Tab bars automatically compact on iPhone
TabView {
    // Tabs get new appearance
}
```

### Toolbar Customization

#### Toolbar Spacer API

```swift
.toolbar {
    ToolbarItemGroup(placement: .topBarTrailing) {
        Button("Up") { }
        Button("Down") { }

        // Fixed spacer separates button groups
        ToolbarSpacer(.fixed)

        Button("Settings") { }
    }
}
```

#### ToolbarItemGroup for Visual Grouping

Items within a `ToolbarItemGroup` share a single Liquid Glass background, creating a visual "pill" for related actions:

```swift
.toolbar {
    ToolbarItem(placement: .cancellationAction) {
        Button("Cancel", systemImage: "xmark") {}
    }

    ToolbarItemGroup(placement: .primaryAction) {
        Button("Draw", systemImage: "pencil") {}
        Button("Erase", systemImage: "eraser") {}
    }

    ToolbarSpacer(.flexible)

    ToolbarItem(placement: .confirmationAction) {
        Button("Save", systemImage: "checkmark") {}
    }
}
```

**Key insight**: `ToolbarItemPlacement` now controls visual appearance, not just position. `confirmationAction` automatically applies `glassProminent` styling; `cancellationAction` uses standard glass.

#### Prominent Tinted Buttons in Liquid Glass

```swift
Button("Add Trip") {
    addTrip()
}
.buttonStyle(.borderedProminent)
.tint(.blue)
// Liquid Glass toolbars support tinting for prominence
```

Toolbar items also support `.badge()` for notification counts:

```swift
ToolbarItem(placement: .confirmationAction) {
    Button("Done", systemImage: "checkmark") { }
        .badge(3)  // Badge count on glass toolbar item
}
```

#### Removing Items from Group Background

Some toolbar items should appear without the shared group background, like an avatar or standalone icon. Apply `sharedBackgroundVisibility(.hidden)` to separate an item visually:

```swift
.toolbar {
    ToolbarItem(placement: .topBarTrailing) {
        // Avatar appears without glass background pill
        Image(systemName: "person.crop.circle")
            .sharedBackgroundVisibility(.hidden)
    }
}
```

#### Monochrome Icon Rendering

Icons use monochrome rendering in more places, including toolbars. This reduces visual noise, emphasizes content, and maintains legibility. You can still tint icons with `.tint()`, but use it to convey meaning (call to action, next step) — not just for visual effect.

### Toolbar Transitions & Morphing

iOS 26 toolbars automatically morph between screens during NavigationStack push/pop transitions. The key insight: attach `.toolbar {}` to individual views inside NavigationStack, not to NavigationStack itself.

#### DefaultToolbarItem

**iOS 26+**. A toolbar item representing a system component. Use it to reposition system-provided items (like search) within your toolbar layout.

```swift
struct DefaultToolbarItem {
    init(kind: ToolbarDefaultItemKind, placement: ToolbarItemPlacement = .automatic)
}
```

**Key semantic**: If the system has already placed a matching item `kind` in the toolbar, `DefaultToolbarItem` implicitly replaces the default-placed instance. This lets you move system items to different placements or reposition them relative to your own toolbar content.

##### Repositioning search between toolbar items

```swift
NavigationSplitView {
    AllCalendarsView()
} detail: {
    SelectedCalendarView()
        .searchable(text: $query)
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                CalendarPicker()
            }
            ToolbarItem(placement: .bottomBar) {
                Invites()
            }
            DefaultToolbarItem(kind: .search, placement: .bottomBar)
            ToolbarSpacer(placement: .bottomBar)
            ToolbarItem(placement: .bottomBar) { NewEventButton() }
        }
}
```

##### Specifying search column in collapsed NavigationSplitView

Place `DefaultToolbarItem` with `.search` kind in the column that should display search when the split view collapses to compact (iPhone):

```swift
NavigationSplitView {
    SidebarView()
        .toolbar {
            DefaultToolbarItem(kind: .search, placement: .bottomBar)
        }
} content: {
    ContentView()
} detail: {
    DetailView()
}
.searchable(text: $text)
```

This only applies when `.searchable()` is placed on the `NavigationSplitView` itself (not a child view).

##### Availability check for backward compatibility

```swift
.toolbar {
    if #available(iOS 26.0, *) {
        DefaultToolbarItem(kind: .search, placement: .bottomBar)
        ToolbarSpacer(.flexible, placement: .bottomBar)
    }
    ToolbarItem(placement: .bottomBar) {
        NewNoteButton()
    }
}
.searchable(text: $searchText)
```

#### Basic Morphing Setup

Each view declares its own toolbar. iOS 26 morphs between them during navigation:

```swift
struct MailboxList: View {
    var body: some View {
        List(mailboxes) { mailbox in
            NavigationLink(mailbox.name, value: mailbox)
        }
        .toolbar {  // ← Attached to this view, not NavigationStack
            ToolbarItem(placement: .bottomBar) {
                Button("Filter", systemImage: "line.3.horizontal.decrease") { }
            }
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(placement: .bottomBar) {
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}

struct MessageList: View {
    let mailbox: Mailbox

    var body: some View {
        List(mailbox.messages) { message in
            MessageRow(message: message)
        }
        .toolbar {  // ← Different toolbar — iOS 26 morphs between them
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(placement: .bottomBar) {
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}
```

**#1 gotcha**: If you attach `.toolbar {}` to the NavigationStack itself, iOS 26 has nothing to morph between — the toolbar stays static across all pushes.

#### Stable Items with `toolbar(id:)` and `ToolbarItem(id:)`

Items with matching IDs across screens stay in place during morphing (no bounce). Unmatched items animate in/out:

```swift
struct MailboxList: View {
    var body: some View {
        List(mailboxes) { mailbox in
            NavigationLink(mailbox.name, value: mailbox)
        }
        .toolbar(id: "main") {
            ToolbarItem(id: "filter", placement: .bottomBar) {
                Button("Filter", systemImage: "line.3.horizontal.decrease") { }
            }
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(id: "compose", placement: .bottomBar) {
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}

struct MessageList: View {
    let mailbox: Mailbox

    var body: some View {
        List(mailbox.messages) { message in
            MessageRow(message: message)
        }
        .toolbar(id: "main") {
            // "filter" absent — animates out during push
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(id: "compose", placement: .bottomBar) {
                // Same ID as MailboxList — stays stable during morph
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}
```

#### Complete Mail-Style Example

Combines DefaultToolbarItem, ToolbarSpacer, and stable compose button:

```swift
struct MailApp: View {
    var body: some View {
        NavigationStack {
            MailboxList()
                .navigationDestination(for: Mailbox.self) { mailbox in
                    MessageList(mailbox: mailbox)
                }
                .navigationDestination(for: Message.self) { message in
                    MessageDetail(message: message)
                }
        }
    }
}

// Each destination defines its own toolbar — NavigationStack morphs between them
```

#### ToolbarSpacer(.flexible)

Pushes toolbar items apart (like Spacer in HStack). Complements `.fixed` for visual separation:

```swift
.toolbar {
    ToolbarItem(placement: .bottomBar) {
        Button("Archive", systemImage: "archivebox") { }
    }
    ToolbarSpacer(.flexible, placement: .bottomBar)  // Push apart
    ToolbarItem(placement: .bottomBar) {
        Button("Compose", systemImage: "square.and.pencil") { }
    }
}
```

#### .navigationSubtitle()

Add a secondary line below the navigation title:

```swift
.navigationTitle("Inbox")
.navigationSubtitle("3 unread messages")
```

### User-Customizable Toolbars

`toolbar(id:content:)` also enables **user customization** — letting people rearrange, show, and hide toolbar items. This is the original purpose of the identified toolbar API (iOS 14+, iPadOS 16+ for customization).

#### Setup

```swift
TextEditor(text: $text)
    .toolbar(id: "editingtools") {
        ToolbarItem(id: "bold", placement: .secondaryAction) {
            Toggle(isOn: $bold) { Image(systemName: "bold") }
        }
        ToolbarItem(id: "italic", placement: .secondaryAction) {
            Toggle(isOn: $italic) { Image(systemName: "italic") }
        }
    }
```

**Platform constraint**: Only `.secondaryAction` items support customization on iPadOS. Other placements follow normal rules and cannot be customized by the user.

#### Controlling visibility

```swift
ToolbarItem(id: "advanced", placement: .secondaryAction, showsByDefault: false) {
    // Hidden by default — user can add from customization editor
    AdvancedFormattingControls()
}
```

#### macOS toolbar customization menu

Add `ToolbarCommands()` to enable the Customize Toolbar menu item:

```swift
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            ToolbarCommands()
        }
    }
}
```

Users can also Control-click the toolbar to access the customization editor.

#### Related types

- `CustomizableToolbarContent` — Protocol for content that supports customization
- `ToolbarCustomizationBehavior` — Control whether items can be added/removed
- `ToolbarCustomizationOptions` — Options for the customization experience

#### ToolbarSpacer in customizable toolbars

Spacers are customizable items too — users can add, remove, and rearrange them from the customization panel. If a customizable toolbar supports a spacer of a given type, users can also add multiple copies:

```swift
ContentView()
    .toolbar(id: "main-toolbar") {
        ToolbarItem(id: "tag") { TagButton() }
        ToolbarItem(id: "share") { ShareButton() }
        ToolbarSpacer(.fixed)
        ToolbarItem(id: "more") { MoreButton() }
    }
```

### Scroll Edge Effects

#### Automatic blur on scroll edges

```swift
ScrollView {
    // When content scrolls under toolbar/navigation bar,
    // blur effect automatically ensures bar content remains legible
    ForEach(trips) { trip in
        TripRow(trip: trip)
    }
}
// No code required - automatic scroll edge blur
```

### Bottom-Aligned Search

**Foundational search APIs** For `.searchable`, `isSearching`, suggestions, scopes, tokens, and programmatic search control, see `axiom-swiftui-search-ref`. This section covers iOS 26 refinements only.

#### iPhone ergonomics

```swift
NavigationSplitView {
    List { }
        .searchable(text: $searchText)
}
// Placement on NavigationSplitView automatically:
// - Bottom-aligned on iPhone (more ergonomic)
// - Top trailing corner on iPad
```

To restore pre-iOS 26 sidebar-embedded search on iPad, specify `placement: .sidebar`:

```swift
NavigationSplitView {
    List { }
        .searchable(text: $searchText, placement: .sidebar)
        // Search field embedded in sidebar instead of floating glass container
}
```

#### System Auto-Minimization

Depending on device size, number of toolbar buttons, and other factors, the system may choose to minimize the search field into a toolbar button automatically. When tapped, a full-width search field appears above the keyboard. Use `searchToolbarBehavior(.minimize)` to explicitly opt in to this behavior when search isn't the main part of your experience.

#### Search Tab Role

Searching in multi-tab apps is often done as a dedicated page. Set a search role on one of your tabs and place `.searchable` on the TabView:

```swift
TabView {
    BrowseView()
        .tabItem { Label("Browse", systemImage: "square.grid.2x2") }

    Tab(role: .search) {
        SearchView()
    }
}
.searchable(text: $searchText)
```

When someone selects the search tab, the search field takes the place of the tab bar. People can interact with browsing suggestions or tap the search field to bring up the keyboard.

On iPad and Mac, when someone selects the search tab, the search field appears **centered above the app's browsing suggestions** — a different layout than iPhone's tab-bar replacement.

See swiftui-nav-ref skill Section 5.5 (iOS 26 Tab Features) for full `Tab(role: .search)` patterns.

### Glass Effect for Custom Views

```swift
struct ToTopButton: View {
    var body: some View {
        Button("To Top", systemImage: "chevron.up") {
            scrollToTop()
        }
        .padding()
        .glassEffect() // Reflects surrounding content
    }

    func scrollToTop() { }
}
```

SwiftUI automatically uses a **vibrant text color** that adapts to maintain legibility against colorful backgrounds. Tints also use vibrant color that adapts to the content behind them.

On iOS, add the `.interactive` modifier to the glass effect for custom controls or containers with interactive elements. Glass reacts to user interaction by scaling, bouncing, and shimmering.

#### GlassEffectContainer

Combine multiple glass elements with `GlassEffectContainer`. This is **essential for visual correctness** — glass samples content from an area larger than itself, but glass cannot sample other glass. Nearby glass elements in different containers produce inconsistent behavior:

```swift
GlassEffectContainer {
    HStack(spacing: 12) {
        ForEach(badges) { badge in
            BadgeView(badge: badge)
                .glassEffect()
        }
    }
}
```

#### glassEffectID for Morphing Transitions

Use `glassEffectID` with a namespace to create fluid morphing transitions between glass elements:

```swift
@Namespace private var badgeNamespace

var body: some View {
    if isExpanded {
        // Expanded badge stack
        GlassEffectContainer {
            VStack {
                ForEach(badges) { badge in
                    BadgeView(badge: badge)
                        .glassEffect()
                        .glassEffectID(badge.id, in: badgeNamespace)
                }
            }
        }
    } else {
        // Collapsed into toolbar button
        Button("Badges", systemImage: "star.fill") {
            isExpanded.toggle()
        }
        .glassEffect()
        .glassEffectID("collapsed", in: badgeNamespace)
    }
}
// Tapping the button expands badges with fluid glass morphing;
// tapping again re-absorbs them into the button
```

### Sheets with Liquid Glass

#### Remove presentationBackground

Partial height sheets are inset by default with a Liquid Glass background. At smaller heights, bottom edges pull in, nesting in the curved edges of the display. When transitioning to full height, the glass gradually becomes opaque and anchors to the screen edge.

If you've used `presentationBackground` to apply a custom background to sheets, consider removing it and let the new material shine:

```swift
// ❌ Custom background interferes with Liquid Glass sheet material
.sheet(isPresented: $showDetail) {
    DetailView()
        .presentationBackground(.thinMaterial)  // Remove this
}

// ✅ System applies Liquid Glass automatically
.sheet(isPresented: $showDetail) {
    DetailView()
}
```

#### Sheet Morphing from Buttons

Sheets can morph directly out of the buttons that present them. Use `navigationZoomTransition` to connect the source view to the sheet content:

```swift
.toolbar {
    ToolbarItem(placement: .topBarTrailing) {
        Button("Details", systemImage: "info.circle") {
            showDetails = true
        }
        .matchedTransitionSource(id: "details", in: namespace)
    }
}
.sheet(isPresented: $showDetails) {
    DetailsSheet()
        .navigationTransition(.zoom(sourceID: "details", in: namespace))
}
```

Menus, alerts, and popovers also flow smoothly out of Liquid Glass controls. Dialogs automatically morph out of the buttons that present them — no additional code needed.

### System Controls Updates

Controls now have the new design automatically:
- Toggles
- Segmented pickers
- Sliders

**Reference** "Build a SwiftUI app with the new design" (WWDC 2025-323) for adoption best practices and advanced customizations.

### Button Shape Changes

Bordered buttons now have a **capsule shape** by default, harmonious with the curved corners of the new design. On macOS, mini, small, and medium size controls retain a rounded-rectangle shape to preserve horizontal density.

```swift
// Capsule is now the default bordered shape on iOS
Button("Action") { }
    .buttonStyle(.bordered)  // Capsule shape on iOS 26

// Override with buttonBorderShape if needed
Button("Action") { }
    .buttonStyle(.bordered)
    .buttonBorderShape(.roundedRectangle)  // Force rounded rectangle
```

#### Extra Large Buttons

For prominent actions, iOS 26 adds support for extra large sized buttons:

```swift
Button("Get Started") { startOnboarding() }
    .buttonStyle(.borderedProminent)
    .controlSize(.extraLarge)  // New extra-large size
```

### Updated Control Heights

Control heights are updated for the new design. Most controls on macOS are slightly taller, providing more breathing room around labels and enhancing click targets.

For compatibility with existing high-density layouts (complex inspectors, popovers), apply `controlSize` to a single control or an entire container:

```swift
// Preserve density in complex inspectors
Form {
    // controls here
}
.controlSize(.small)  // Maintains pre-iOS 26 density
```

### Menu Cross-Platform Consistency

Menus across platforms have a new design and more consistent layout. Icons are consistently on the leading edge and are now used on macOS too. The same API using `Label` or standard control initializers now creates the same result on both platforms:

```swift
Menu("Actions") {
    Button("Copy", systemImage: "doc.on.doc") { copy() }
    Button("Paste", systemImage: "doc.on.clipboard") { paste() }
    Button("Delete", systemImage: "trash", role: .destructive) { delete() }
}
// Same result on iOS and macOS — icons on leading edge
```

### Concentric Rectangle Shape

Controls align their corners perfectly within their containers — even the device itself. This is **corner concentricity**. A button at the bottom of a sheet should share the same corner center with the sheet.

```swift
// Automatic concentricity with container
Button("Confirm") { confirm() }
    .clipShape(.rect(cornerRadius: 12, style: .containerConcentric))
    // Shape automatically matches container across displays and window shapes
```

**Use case**: Buttons positioned at the edges of sheets, popovers, or cards that need their corners to feel visually nested within the container.

---

## Slider Enhancements

iOS 26 introduces major enhancements to `Slider`: custom tick marks, constrained selection ranges, current value labels, and thumb visibility control.

### Slider Ticks API

#### Core Types

| Type | Purpose |
|------|---------|
| `SliderTick<V>` | Individual tick at a specific value with optional label |
| `SliderTickContentForEach` | Iterate over collection to create multiple ticks |
| `SliderTickBuilder` | Result builder for composing tick content |
| `TupleSliderTickContent` | Internal type for multiple inline ticks |
| `SliderTickContent` | Protocol that all tick types conform to |

### Basic Ticks

#### Static tick marks

```swift
struct SpeedSlider: View {
    @State private var speed: Double = 0.5

    var body: some View {
        Slider(value: $speed) {
            Text("Speed")
        } ticks: {
            SliderTick(0.2)
            SliderTick(0.5)
            SliderTick(0.8)
        }
    }
}
```

### Labeled Ticks

#### Ticks with custom labels

```swift
Slider(value: $value, in: 0...10) {
    Text("Rating")
} ticks: {
    SliderTick(0) { Text("Min") }
    SliderTick(5) { Text("Mid") }
    SliderTick(10) { Text("Max") }
}
```

### Dynamic Ticks with SliderTickContentForEach

#### Iterate over values to create ticks

```swift
struct TemperatureSlider: View {
    @State private var temp: Float = 70

    var body: some View {
        let stops: [Float] = stride(from: 60, through: 80, by: 5).map { Float($0) }

        Slider(value: $temp, in: 60...80) {
            Text("Temperature")
        } ticks: {
            SliderTickContentForEach(stops, id: \.self) { value in
                SliderTick(value) {
                    Text("\(Int(value))°")
                        .font(.caption2)
                }
            }
        }
    }
}
```

### Custom Data with Ticks (API Constraint)

**Important** `SliderTickContentForEach` requires `Data.Element` to match the `SliderTick<V>` value type. You cannot iterate directly over custom structs.

#### ❌ This won't compile

```swift
struct Chapter {
    let time: Double
    let name: String
    let id: UUID
}

// ERROR: Data.Element (Chapter) doesn't match SliderTick value type (Double)
SliderTickContentForEach(chapters, id: \.id) { chapter in
    SliderTick(chapter.time) { Text(chapter.name) }
}
```

#### ✅ Workaround: Extract values, look up labels

```swift
struct ChapterSlider: View {
    @Binding var currentTime: Double
    let chapters: [Chapter]
    let duration: Double

    var body: some View {
        Slider(value: $currentTime, in: 0...duration) {
            Text("Time")
        } ticks: {
            // Iterate over Double values, not Chapter structs
            SliderTickContentForEach(chapters.map(\.time), id: \.self) { time in
                SliderTick(time) {
                    // Look up chapter name for label
                    if let chapter = chapters.first(where: { $0.time == time }) {
                        Text(chapter.name)
                            .font(.caption2)
                    }
                }
            }
        }
    }
}
```

**Why** The API ties tick positions to slider values (`BinaryFloatingPoint`). The type system enforces this so ticks align correctly with the slider's value range.

### Advanced Slider Initializers

#### Full-featured slider with ticks

```swift
Slider(
    value: $rating,
    in: 0...100,
    neutralValue: 50,           // Starting point / center value
    enabledBounds: 20...80,     // Restrict selectable range
    label: { Text("Rating") },
    currentValueLabel: { Text("\(Int(rating))") },
    minimumValueLabel: { Text("0") },
    maximumValueLabel: { Text("100") },
    ticks: {
        SliderTick(20) { Text("Min") }
        SliderTick(50) { Text("Neutral") }
        SliderTick(80) { Text("Max") }
    },
    onEditingChanged: { editing in
        print(editing ? "Started" : "Ended")
    }
)
```

#### Parameters

| Parameter | Type | Purpose |
|-----------|------|---------|
| `value` | `Binding<V>` | Current slider value |
| `bounds` | `ClosedRange<V>` | Full value range (default: `0...1`) |
| `step` | `V.Stride` | Increment between valid values |
| `neutralValue` | `V?` | Starting/center point |
| `enabledBounds` | `ClosedRange<V>?` | Restrict which values are selectable |
| `ticks` | `@SliderTickBuilder` | Custom tick marks |
| `currentValueLabel` | `@ViewBuilder` | Shows current value |
| `onEditingChanged` | `(Bool) -> Void` | Called when editing starts/ends |

### Step-Based Ticks

#### Automatic ticks at each step

```swift
Slider(
    value: $volume,
    in: 0...10,
    step: 2,
    label: { Text("Volume") },
    tick: { value in
        // Called for each step value (0, 2, 4, 6, 8, 10)
        SliderTick(value) {
            Text("\(Int(value))")
        }
    }
)
```

### sliderThumbVisibility

#### Hide slider thumb for minimal interfaces

```swift
struct MediaControlView: View {
    @State private var progress: CGFloat = 0.5

    var body: some View {
        Slider(value: $progress)
            .sliderThumbVisibility(.hidden)
            .padding(.horizontal, 16)
    }
}
```

**Visibility options**
- `.automatic` — System default (usually visible)
- `.visible` — Always show thumb
- `.hidden` — Hide thumb

**Use cases**
- Media player progress indicators
- Read-only value displays
- Minimal UI designs where slider acts as progress view
- Interactive sliders where visual focus should be on track, not thumb

**Note** On watchOS, the slider thumb is always visible regardless of this setting.

### Complete Media Player Example

```swift
struct MediaPlayerControls: View {
    @State private var currentTime: Double = 0
    let duration: Double = 300 // 5 minutes
    let chapters: [Chapter] = [
        Chapter(time: 0, name: "Intro", id: UUID()),
        Chapter(time: 60, name: "Verse 1", id: UUID()),
        Chapter(time: 120, name: "Chorus", id: UUID()),
        Chapter(time: 180, name: "Verse 2", id: UUID()),
        Chapter(time: 240, name: "Outro", id: UUID())
    ]

    var body: some View {
        VStack {
            // Time display
            HStack {
                Text(formatTime(currentTime))
                Spacer()
                Text(formatTime(duration))
            }
            .font(.caption)

            // Slider with chapter ticks
            Slider(
                value: $currentTime,
                in: 0...duration,
                label: { Text("Playback") },
                currentValueLabel: {
                    if let chapter = currentChapter {
                        Text(chapter.name)
                            .font(.caption)
                    }
                },
                ticks: {
                    SliderTickContentForEach(chapters.map(\.time), id: \.self) { time in
                        SliderTick(time)
                    }
                }
            )
            .sliderThumbVisibility(.hidden)
        }
        .padding()
    }

    var currentChapter: Chapter? {
        chapters.last { $0.time <= currentTime }
    }

    func formatTime(_ seconds: Double) -> String {
        let mins = Int(seconds) / 60
        let secs = Int(seconds) % 60
        return String(format: "%d:%02d", mins, secs)
    }
}
```

---

## New View Modifiers

### safeAreaBar

#### Sticky bars with progressive blur

```swift
struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                ForEach(1...20, id: \.self) { index in
                    Text("\(index). Item")
                }
            }
            .safeAreaBar(edge: .bottom) {
                Text("Bottom Action Bar")
                    .padding(.vertical, 15)
            }
            .scrollEdgeEffectStyle(.soft, for: .bottom)
            // Alternative: .scrollEdgeEffectStyle(.hard, for: .bottom)
        }
    }
}
```

**Features**
- Works like `safeAreaInset` but with integrated blur
- Progressive blur (`.soft`) or hard blur (`.hard`) via `scrollEdgeEffectStyle`
- Automatically respects safe areas
- Bar remains fixed while content scrolls beneath

**Use cases**
- Action bars that remain visible while scrolling
- Fixed controls at screen edges
- Bottom toolbars with scroll blur

### onOpenURL Enhancement

#### Open links in in-app browser

```swift
struct LinkView: View {
    @Environment(\.openURL) var openURL

    var body: some View {
        let website = URL(string: "https://example.com")!

        VStack {
            // Old style - opens in Safari
            Link(destination: website) {
                Text("Open in Safari")
            }

            // New style - opens in-app (iOS 26+)
            Button("Open In-App") {
                openURL(website, prefersInApp: true)
            }
            .buttonStyle(.borderedProminent)
        }
    }
}
```

**Key difference**
- Default `Link` opens in Safari app
- `openURL(url, prefersInApp: true)` opens in SFSafariViewController-style in-app browser
- Keeps users in your app
- Preserves navigation flow

### Button Roles (.close and .confirm)

#### System-styled close and confirm buttons

```swift
struct ModalView: View {
    @State private var showSheet = false

    var body: some View {
        Button("Show Sheet") {
            showSheet.toggle()
        }
        .sheet(isPresented: $showSheet) {
            NavigationStack {
                VStack {}
                    .navigationTitle("Info")
                    .toolbar {
                        ToolbarSpacer(.flexible, placement: .topBarTrailing)

                        ToolbarItem(placement: .topBarTrailing) {
                            Button(role: .close) {
                                showSheet = false
                            }
                        }
                    }
            }
            .presentationDetents([.medium])
        }
    }
}
```

**Features**
- `Button(role: .close)` renders as X icon with glass effect in toolbars
- `Button(role: .confirm)` provides system-styled confirmation button
- No custom label needed
- Consistent with system modals and sheets

**Use cases**
- Modal dismissal
- Sheet close buttons
- Confirmation dialogs
- Native-looking dismiss actions

### GlassButtonStyle (iOS 26.1+)

#### Glass button variations

```swift
struct GlassButtonExample: View {
    var body: some View {
        ZStack {
            Image(.background)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .ignoresSafeArea()

            VStack(spacing: 16) {
                Button("Clear Glass") {}
                    .buttonStyle(GlassButtonStyle(.clear))

                Button("Regular Glass") {}
                    .buttonStyle(GlassButtonStyle(.glass))

                Button("Tinted Glass") {}
                    .buttonStyle(GlassButtonStyle(.tint))
                    .tint(.blue)
            }
            .fontWeight(.bold)
            .foregroundStyle(.white)
            .buttonSizing(.flexible)
            .font(.title)
            .padding()
        }
    }
}
```

**Variants**
- `.clear` — Transparent glass effect
- `.glass` — Standard glass appearance
- `.tint` — Colored glass (use with `.tint()` modifier)

**Requires** iOS 26.1+ (not available in initial iOS 26.0 release)

### buttonSizing

#### Control button layout behavior

```swift
struct ButtonLayoutExample: View {
    var body: some View {
        VStack(spacing: 16) {
            Button("Fit Content") {}
                .buttonSizing(.fit)
            // Button shrinks to label size

            Button("Stretch Full Width") {}
                .buttonSizing(.stretch)
            // Button expands to fill available space

            Button("Flexible") {}
                .buttonSizing(.flexible)
            // Balanced between fit and stretch
        }
        .padding()
    }
}
```

**Options**
- `.fit` — Button fits label size
- `.stretch` — Button fills available width
- `.flexible` — Balanced sizing (context-dependent)

**Works with**
- Plain text buttons
- Custom labels (icon + text, HStack/VStack)
- All button styles

### searchToolbarBehavior

**Foundational search APIs** For the `.searchable` modifier, placement options, and search suggestions that `.searchToolbarBehavior` builds upon, see `axiom-swiftui-search-ref`.

#### Compact search that expands on focus

```swift
struct SearchView: View {
    @State private var searchText = ""

    var body: some View {
        NavigationStack {
            List {
                Text("User 1")
                Text("User 2")
                Text("User 3")
            }
            .navigationTitle("Search Users")
            .searchable(text: $searchText)
            .searchToolbarBehavior(.minimize)
            .toolbar {
                ToolbarSpacer(.flexible, placement: .bottomBar)
                DefaultToolbarItem(kind: .search, placement: .bottomBar)
            }
        }
    }
}
```

**`SearchToolbarBehavior` options**:
- `.minimize` — Search field compact (button-like) when unfocused, expands on tap
- `.automatic` — System default behavior (full search field)

**Behavior**
- Similar to Tab Bar search pattern
- Saves toolbar space
- Cleaner UI when search not in use

**Use cases**
- List/content-heavy screens
- Crowded navigation bars
- Tab bar style search on regular screens

**Backward-compatible wrapper** for apps targeting iOS 18+26:

```swift
extension View {
    @ViewBuilder func minimizedSearch() -> some View {
        if #available(iOS 26.0, *) {
            self.searchToolbarBehavior(.minimize)
        } else { self }
    }
}

// Usage
.searchable(text: $searchText)
.minimizedSearch()
```

### searchPresentationToolbarBehavior (iOS 17.1+)

#### Prevent title hiding during search

```swift
.searchable(text: $searchText)
.searchPresentationToolbarBehavior(.avoidHidingContent)
```

**Behavior**
- By default, navigation title hides when search becomes active
- `.avoidHidingContent` keeps title visible during search
- Maintains context while searching

**Note** This modifier was introduced in iOS 17.1, not iOS 26, but complements the new `searchToolbarBehavior` modifier.

---

## iPad Enhancements

### Menu Bar

#### Access common actions via swipe-down menu

```swift
.commands {
    TextEditingCommands() // Same API as macOS menu bar

    CommandGroup(after: .newItem) {
        Button("Add Note") {
            addNote()
        }
        .keyboardShortcut("n", modifiers: [.command, .shift])
    }
}
// Creates menu bar on iPad when people swipe down
```

### Resizable Windows

#### Fluid resizing on iPad

```swift
// MIGRATION REQUIRED:
// Remove deprecated property list key in iPadOS 26:
// UIRequiresFullscreen (entire key deprecated, all values)

// For split view navigation, system automatically shows/hides columns
// based on available space during resize
NavigationSplitView {
    Sidebar()
} detail: {
    Detail()
}
// Adapts to resizing automatically
```

**Reference** "Elevate the design of your iPad app" (WWDC 2025)

---

## macOS Window Enhancements

### Synchronized Window Resize Animations

```swift
.windowResizeAnchor(.topLeading) // Tailor where animation originates

// SwiftUI now synchronizes animation between content view size changes
// and window resizing - great for preserving continuity when switching tabs
```

---

## Performance Improvements

### List Performance (macOS Focus)

#### Massive gains for large lists

- **6x faster loading** for lists of 100,000+ items on macOS
- **16x faster updates** for large lists
- Even bigger gains for larger lists
- Improvements benefit all platforms (iOS, iPadOS, watchOS)

```swift
List(trips) { trip in // 100k+ items
    TripRow(trip: trip)
}
// Loads 6x faster, updates 16x faster on macOS (iOS 26+)
```

### Scrolling Performance

#### Reduced dropped frames

SwiftUI has improved scheduling of user interface updates on iOS and macOS. This improves responsiveness and lets SwiftUI do even more work to prepare for upcoming frames. All in all, it reduces the chance of your app dropping a frame while scrolling quickly at high frame rates.

### Nested ScrollViews with Lazy Stacks

#### Photo carousels and multi-axis scrolling

```swift
ScrollView(.horizontal) {
    LazyHStack {
        ForEach(photoSets) { photoSet in
            ScrollView(.vertical) {
                LazyVStack {
                    ForEach(photoSet.photos) { photo in
                        PhotoView(photo: photo)
                    }
                }
            }
        }
    }
}
// Nested scrollviews now properly delay loading with lazy stacks
// Great for building photo carousels
```

### SwiftUI Performance Instrument

#### New profiling tool in Xcode

Available lanes:
- **Long view body updates** — Identify expensive body computations
- **Platform view updates** — Track UIKit/AppKit bridging performance
- Other performance problem areas

**Reference** "Optimize SwiftUI performance with instruments" (WWDC 2025)

**Cross-reference** [SwiftUI Performance](/skills/ui-design/swiftui-performance) — Master the SwiftUI Instrument

---

## Swift Concurrency Integration

### Compile-Time Data Race Safety

```swift
@Observable
class TripStore {
    var trips: [Trip] = []

    func loadTrips() async {
        trips = await TripService.fetchTrips()
        // Swift 6 verifies data race safety at compile time
    }
}
```

**Benefits** Find bugs in concurrent code before they affect your app

#### References
- "Embracing Swift concurrency" (WWDC 2025)
- "Explore concurrency in SwiftUI" (WWDC 2025)

**Cross-reference** [Swift Concurrency](/skills/concurrency/swift-concurrency) — Swift 6 strict concurrency patterns

---

## @Animatable Macro

### Overview

Simplifies custom animations by automatically synthesizing `animatableData` property.

#### Before (@Animatable macro)

```swift
struct HikingRouteShape: Shape {
    var startPoint: CGPoint
    var endPoint: CGPoint
    var elevation: Double
    var drawingDirection: Bool // Don't want to animate this

    // Tedious manual animatableData declaration
    var animatableData: AnimatablePair<CGPoint.AnimatableData,
                        AnimatablePair<Double, CGPoint.AnimatableData>> {
        get {
            AnimatablePair(startPo

…(truncated)
