WidgetKit
Build home screen widgets, Lock Screen widgets, Control Center controls, and StandBy or CarPlay widget surfaces for iOS 26+.
Keep adjacent-framework guidance scoped to WidgetKit integration. Include
ActivityKit and App Intents only where they connect directly to WidgetKit
surfaces; hand off full lifecycle, APNs content-state, Siri/Shortcuts/Spotlight,
or entity-modeling work to sibling activitykit or app-intents skills.
See references/widgetkit-advanced.md [blocked] for timeline strategies, push-based updates, Xcode setup, and advanced patterns.
Contents
- Workflow
- Widget Protocol and WidgetBundle
- Configuration Types
- TimelineProvider
- AppIntentTimelineProvider
- Widget Families
- Interactive Widgets (iOS 17+)
- ActivityConfiguration Handoff
- Control Center Widgets (iOS 18+)
- Lock Screen Widgets
- StandBy Mode
- Widget URL Handling and Deep Links
- Smart Stack Relevance
- Design Patterns
- iOS 26 Additions
- Common Mistakes
- Review Checklist
- References
Workflow
1. Create a new widget
- Add a Widget Extension target in Xcode (File > New > Target > Widget Extension).
- Enable App Groups for shared data between the app and widget extension.
- Define a
TimelineEntrystruct with adateproperty and display data. - Implement a
TimelineProvider(static) orAppIntentTimelineProvider(configurable). - Build the widget view using SwiftUI, adapting layout per
WidgetFamily. - Declare the
Widgetconforming struct with a configuration and supported families. - Register all widgets in a
WidgetBundleannotated with@main.
2. Integrate adjacent surfaces
- Register an
ActivityConfigurationin the widget bundle when the app has a Live Activity, but keepActivityAttributes, request/update/end, APNscontent-state, and Dynamic Island layout depth inactivitykit. - Place
Button,Toggle,ControlWidgetButton, andControlWidgetTogglein WidgetKit views or controls, but keep intent modeling, entities, queries, Siri, Shortcuts, and Spotlight inapp-intents.
3. Add a Control Center control
- Reuse an
AppIntent/OpenIntentfor a button, or aSetValueIntentfor a toggle. - Create a
ControlWidgetButtonorControlWidgetTogglein the widget bundle. - Use
StaticControlConfigurationorAppIntentControlConfiguration.
4. Review existing widget code
Run through the Review Checklist at the end of this document.
Widget Protocol and WidgetBundle
Widget
Every widget conforms to the Widget protocol and returns a WidgetConfiguration
from its body.
WidgetBundle
Use WidgetBundle to expose multiple widgets from a single extension.
Configuration Types
Use StaticConfiguration for non-configurable widgets. Use AppIntentConfiguration
(recommended) for configurable widgets paired with AppIntentTimelineProvider.
Shared Modifiers
TimelineProvider
For static (non-configurable) widgets. Uses completion handlers. Three required methods:
AppIntentTimelineProvider
For configurable widgets. Uses async/await natively. Receives user intent configuration.
Widget Families
Adapt layout per family using @Environment(\.widgetFamily):
Interactive Widgets (iOS 17+)
Use Button and Toggle with intent types available to the widget extension or
shared code. WidgetKit owns the view placement; app-intents owns intent
modeling and behavior.
ActivityConfiguration Handoff
WidgetKit registers Live Activity surfaces in the widget extension. Keep this
section to registration and rendering handoff; use activitykit for
ActivityAttributes, lifecycle, push updates, and full Dynamic Island patterns.
Control Center Widgets (iOS 18+)
WidgetKit owns control configuration, placement, kind, display name, push
handler, and extension registration. Control actions and value intents belong in
app-intents.
Lock Screen Widgets
Use accessory families and AccessoryWidgetBackground.
StandBy Mode
Small system widgets can appear in StandBy and CarPlay. Use
@Environment(\.widgetLocation) for conditional rendering:
Widget URL Handling and Deep Links
Use one .widgetURL(_:) as the whole-widget fallback route. Use Link for
deliberate subtargets only where the family and layout support them, including
.accessoryRectangular, .systemSmall, and larger system widgets. For small
widgets, prefer one clear fallback; avoid multiple Link targets unless the
visual affordance and hit areas remain unambiguous.
Never attach multiple widgetURL modifiers in the hierarchy.
Smart Stack Relevance
Use TimelineEntryRelevance(score:duration:) on timeline entries for timely
iPhone and iPad Smart Stack relevance. Keep scores on a consistent positive
scale; zero or lower means not relevant.
For configurable widgets, donate App Intents that correspond to user actions or
widget parameters from app-side code, such as with intent.donate() or
IntentDonationManager. Keep AppEntity and EntityQuery design in
app-intents.
On watchOS, contextual relevance uses
WidgetRelevance([WidgetRelevanceAttribute(...)]) from the provider
relevance() callback. That path is not used by iPhone or iPad Smart Stacks.
Design Patterns
- Prefer
Gaugeover manual arcs. Use.gaugeStyle(.accessoryCircular)for Lock Screen circular widgets and.linearCapacityfor home screen capacity bars. The system handles styling, accessibility, and rendering-mode adaptation. - Use
.containerBackground(_:for: .widget)(iOS 17+) for widget backgrounds instead of padding and background modifiers. - Use
Canvasfor dense visualizations like sparklines or mini bar charts. The lack of per-element accessibility is acceptable since the entire widget surface is a single tap target. - Match timeline refresh to data granularity. The budget is dynamic and opportunistic; schedule useful future entries, avoid unnecessary reloads, and use
Text(timerInterval:countsDown:)for live countdowns. Load the advanced reference for current budget guidance.
See references/widgetkit-advanced.md [blocked] for code examples and detailed guidance on each pattern.
iOS 26 Additions
Liquid Glass Support
Adapt widgets to Liquid Glass with @Environment(\.widgetRenderingMode),
.widgetAccentable(), and Image.widgetAccentedRenderingMode(_:). In
.vibrant, the system maps content into the material style, so avoid relying on
original colors alone.
Push Reload Handlers
Widget push reloads:
- Add Push Notifications capability to the widget extension target.
- Keep the
WidgetPushHandlertype in the widget extension target or shared code linked into it, not only in the main app target. - Register the handler with
.pushHandler(...)on the widget configuration. - Do not use User Notifications registration to obtain widget push tokens;
WidgetKit supplies tokens through
pushTokenDidChange(_:widgets:). - Use
apns-push-type: widgets, topic suffix.push-type.widgets, andaps.content-changed. - Treat push as a budgeted, opportunistic reload signal, not state delivery and
not the only freshness model. Timelines, reload policies, shared storage or
refetch, and app-triggered
WidgetCenterreloads remain the fallback path.
Control push reloads:
- Register a
ControlPushHandlerwith.pushHandler(...)on theControlWidgetConfiguration. pushTokensDidChange(controls:)receives[ControlInfo]; read tokens from each control'spushInfo.- Use
apns-push-type: controls, topic suffix.push-type.controls, andaps.content-changed.
CarPlay Widgets
Small system widgets can appear in CarPlay on iOS 26+. Ensure layouts are legible at a glance; taps and controls depend on vehicle touch support and, for opening the app, CarPlay integration.
Common Mistakes
-
Using IntentTimelineProvider instead of AppIntentTimelineProvider.
IntentTimelineProvideris the older SiriKit Intents-based provider. PreferAppIntentTimelineProviderwith the App Intents framework for new widgets. -
Exceeding the refresh budget. Widgets have a daily refresh limit. Do not call
WidgetCenter.shared.reloadTimelines(ofKind:)on every minor data change. Batch updates and use appropriateTimelineReloadPolicyvalues. -
Forgetting App Groups for shared data. The widget extension runs in a separate process. Use
UserDefaults(suiteName:)or a shared App Group container for data the widget reads. -
Performing network calls in placeholder().
placeholder(in:)must return synchronously with sample data. UsegetTimelineortimeline(for:in:)for async work. -
Treating WidgetKit push payloads as state. Widget and control pushes are reload signals. Persist state in shared storage or refetch it in the provider.
-
Registering widget pushes through User Notifications. Widget push tokens come from WidgetKit handlers, not
UNUserNotificationCenter. -
Putting heavy logic in the widget view. Widget views are rendered in a size-limited process. Pre-compute data in the timeline provider and pass display-ready values through the entry.
-
Ignoring accessory rendering modes. Lock Screen widgets render in
.vibrantor.accentedmode, not.fullColor. Test with@Environment(\.widgetRenderingMode)and avoid relying on color alone. -
Not testing on device. StandBy, CarPlay, and accessory rendering differ significantly from Simulator. Always verify on physical hardware.
Review Checklist
- Widget extension target has App Groups entitlement matching the main app
-
@mainis on theWidgetBundle, not on individual widgets -
placeholder(in:)returns synchronously;getSnapshot/snapshot(for:in:)fast whenisPreview - Timeline reload policy matches update frequency;
reloadTimelines(ofKind:)only on data change - Layout adapts per
WidgetFamily; accessory widgets tested in.vibrantmode - Interactive widgets use extension-available App Intents with
Button/Toggleonly - One
.widgetURL(_:)fallback is used;Linksubtargets are family-appropriate - Widget push handlers live in the widget extension/shared code and do not use User Notifications token registration
- Widget/control pushes supplement timelines and shared-state/refetch fallbacks
- Smart Stack relevance uses timeline relevance and app-side intent donations where useful
- Live Activity lifecycle and App Intent modeling are handed off to sibling skills
- Controls use
StaticControlConfiguration/AppIntentControlConfiguration - Timeline entries and Intent types are Sendable; tested on device
References
- Advanced guide: references/widgetkit-advanced.md [blocked]
- Apple docs: WidgetKit | Keeping a widget up to date | Smart Stack visibility


