The iOS 26 Widget Surface: One App Intent, Many Places

For years a widget was a snapshot. The system rendered your view on a timeline, the user looked at it, and any tap opened the app. That model is gone. Since iOS 17 a widget can carry buttons and toggles that run code without launching anything, and the code they run is an App Intent1. The same is true of Control Center controls, and of the buttons inside a Live Activity. Once you see that, the iOS 26 widget surface stops looking like three separate APIs and starts looking like one: a set of places the system will render your App Intents, each with different real estate and the same plumbing underneath.

That reframe is the whole point of this piece. Learn the App Intent once and you light up the Home Screen, the Lock Screen, Control Center, the Action button, the Dynamic Island, and (the same intent again) Siri and Apple Intelligence. Write three disconnected features and you do four times the work for a worse result.

TL;DR

  • Interactive widgets (iOS 17+): put a Button(intent:) or Toggle(isOn:intent:) in a widget view; the system runs the App Intent’s perform() in your widget extension, off-app, then reloads the timeline1.
  • Control Center controls (iOS 18+): a ControlWidget built from ControlWidgetButton or ControlWidgetToggle, backed by the same App Intents, surfaces in Control Center, on the Lock Screen, and on the Action button2.
  • Live Activities (ActivityKit): ActivityAttributes plus an ActivityConfiguration drive the Lock Screen and Dynamic Island; server-driven updates arrive over push, and the buttons inside are App Intents too3.
  • iOS 26 adds the rendering, not the model: widgets pick up Liquid Glass presentation and accented rendering modes, but the App-Intents-as-surface architecture is unchanged from iOS 184.
  • The unifying fact: a widget button, a control, a Siri phrase, and an Apple Intelligence action can all be the same App Intent. Design the capability once.

Interactive widgets: the button that does not open the app

The mechanism is small and worth stating exactly. You add a SwiftUI Button or Toggle to a widget’s view, but instead of an action closure you pass an App Intent1:

struct WaterWidgetView: View {
    let logged: Int

    var body: some View {
        VStack {
            Text("\(logged) glasses")
            Button(intent: LogWaterIntent()) {
                Label("Add", systemImage: "plus")
            }
        }
    }
}

When the user taps, the system runs LogWaterIntent.perform() inside your widget extension. The app does not launch. The intent does its work (write to the shared store, update a count), and the widget timeline reloads to show the result.

A Toggle works the same way through a SetValueIntent, which receives the new on/off value the user just set:

struct ToggleReminderIntent: SetValueIntent {
    static let title: LocalizedStringResource = "Toggle Reminder"

    @Parameter(title: "Enabled")
    var value: Bool

    func perform() async throws -> some IntentResult {
        ReminderStore.shared.enabled = value
        return .result()
    }
}

The widget binds Toggle(isOn: store.enabled, intent: ToggleReminderIntent()), the system sets value to the new state, runs perform(), and reloads. Same shape as the button, one extra parameter1.

Two constraints define what you can do here, and both are easy to violate. First, perform() runs in the constrained widget-extension environment with a tight execution budget; it is for a quick state change, not a network upload or heavy computation. Second, the widget reflects state, it does not animate arbitrary UI. You change data and the timeline re-renders; you do not run a custom transition. Design the interaction as “flip a value, show the new value” and it works. Reach for more and the system fights you.

Control Center controls: the same intent, a different room

iOS 18 opened Control Center, the Lock Screen, and the Action button to third-party controls, and the API is deliberately parallel to widgets2. A control is a ControlWidget, and its body is a ControlWidgetButton or ControlWidgetToggle wired to an App Intent:

struct LogWaterControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "com.example.logWater") {
            ControlWidgetButton(action: LogWaterIntent()) {
                Label("Log Water", systemImage: "drop.fill")
            }
        }
    }
}

If LogWaterIntent already powers your interactive widget, the control is nearly free: same intent, new wrapper. That is the architecture paying off. The control is not a second implementation of the feature; it is a second mounting point for the one you already wrote. A toggle control uses SetValueIntent exactly as the widget toggle does, so a “mute,” “start session,” or “toggle dark mode” capability built once appears in Control Center, on the Lock Screen, and on the Action button without new logic2.

Live Activities: the surface that updates itself

Live Activities are the third place, and the one with the most moving parts. An ActivityAttributes type defines the static and dynamic content, an ActivityConfiguration renders the Lock Screen presentation and the Dynamic Island regions, and ActivityKit starts, updates, and ends the activity3. The buttons inside a Live Activity are, again, App Intents, so a “pause” or “next” control in the Dynamic Island runs the same kind of intent as the widget button.

The piece that makes Live Activities their own discipline is the update path. A timer you update locally is simple. An activity driven by events that happen on a server (a delivery moving, a game score changing) updates over push: you register for pushTokenUpdates, send ActivityKit push payloads from your backend, and the system updates the Lock Screen and Dynamic Island without your app running at all3. That is genuinely powerful and genuinely easy to get wrong, because now your activity’s correctness depends on a server contract, push reliability, and a stale-date policy, not just local code.

What iOS 26 actually changed

The headline for iOS 26 widgets is presentation, not architecture. Widgets adopt the Liquid Glass material and gain accented rendering modes, so they match the system’s new design language and tint correctly in the contexts where the OS recolors content4. That matters for how a widget looks across the Home Screen, Lock Screen, and StandBy, and it is worth a design pass. It does not change how interactivity works. If you built interactive widgets and controls on iOS 18, the iOS 26 move is a visual refresh, not a rewrite. Treat claims that iOS 26 “introduced” interactive widgets with suspicion; the interactivity shipped in iOS 17, the controls in iOS 18, and iOS 26 made them look like the rest of the system.

The design implication of Liquid Glass is real, though. A widget is content the system composites over wallpaper and refracts through glass chrome, so the same rules that govern Liquid Glass in an app apply: respect the functional-versus-content layering, and do not fight the material with heavy custom backgrounds the glass cannot read.

The architecture, stated plainly

Here is the model worth keeping. An App Intent is a named, typed, described capability. iOS gives you a growing set of places to mount that capability:

  • A Button or Toggle inside a widget.
  • A ControlWidget in Control Center, the Lock Screen, and the Action button.
  • A button inside a Live Activity and its Dynamic Island.
  • A Siri phrase and a Shortcuts action.
  • An action Apple Intelligence can invoke on the user’s behalf5.

Every one of those is the same AppIntent type with a perform() method. The work is in designing that capability well: a clear name, typed parameters, a fast and idempotent perform(), and a sensible result. Do that once and the surfaces are wrappers. This is the same argument I have made about App Intents as the real API to your app and about the three surfaces an iOS app now exposes: the widget surface is not a feature you build, it is a place your capabilities show up once they exist.

When not to bother

The widget surface rewards apps with genuine glanceable state and quick actions: a tracker, a timer, a toggle, a now-playing control. It does not reward everything.

  • No glanceable state, no widget. If your app has nothing worth showing without opening it, a widget is decoration that costs you maintenance and an extra extension target.
  • Heavy or slow actions do not belong in perform() here. The widget and control execution environment is constrained. If the action needs real time or real compute, the button should open the app to a prepared state, not pretend to do the work in the extension.
  • A Live Activity for something that is not live. Live Activities are for time-bound, actively-changing events. Using one as a persistent status badge is a misread of the surface and a fast way onto the system’s bad list for activity budget.

The skill is not learning three APIs. It is designing App Intents worth mounting, then mounting them where the user already is: the Home Screen they check, the Control Center they swipe to, the Dynamic Island already showing them something. Build the capability once, with taste, and iOS hands you the surfaces for free.



  1. Apple Developer, “Adding interactivity to widgets and Live Activities”. Interactive widgets (iOS 17+) use SwiftUI Button(intent:) and Toggle(isOn:intent:) backed by an AppIntent (or SetValueIntent for toggles); the intent’s perform() runs in the widget extension without launching the app, and the timeline reloads to reflect the result. 

  2. Apple Developer, “Creating controls to perform actions across the system” and the ControlWidget protocol. Controls (iOS 18+) are built from ControlWidgetButton and ControlWidgetToggle wired to App Intents, and surface in Control Center, on the Lock Screen, and on the Action button. 

  3. Apple Developer, “ActivityKit” and “Displaying live data with Live Activities”. ActivityAttributes and ActivityConfiguration define and render the Lock Screen and Dynamic Island; ActivityKit starts, updates, and ends activities, and server-driven updates use push with pushTokenUpdates

  4. iOS 26 widget rendering: widgets adopt the Liquid Glass material and the accented rendering mode, controlled through WidgetRenderingMode and the \.widgetRenderingMode environment value. The interactivity model (App Intents in widgets and controls) is unchanged from iOS 17 (widgets) and iOS 18 (controls). Apple, “WWDC 2025: the new software design” and the WidgetKit documentation

  5. Apple Developer, “App Intents”. The same AppIntent type is the unit the system exposes through Siri, Shortcuts, Spotlight, the widget and control surfaces, and Apple Intelligence. Author’s analysis of the cross-surface model: App Intents Are Apple’s New API to Your App, App Intents 2 and the iOS 26 additions, and the three surfaces of an iOS app

Artigos relacionados

Live Activities Are a State Machine, Not a Badge

Return's Live Activity looks like a Lock Screen countdown. It is a five-state lifecycle machine with three dismissal pat…

18 min de leitura

Apple's Translation Framework: Free, On-Device, and Sharper Than It Looks

Apple's Translation framework: free on-device translation via translationPresentation and TranslationSession, plus the o…

8 min de leitura

When the Maintainer Is the Attacker: jqwik 1.10.0

jqwik 1.10.0 emits a destructive prompt-injection string in Maven output. ANSI escapes hide it from humans. The maintain…

18 min de leitura