AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Widgetkit

skill-dndungu-agent-skills-widgetkit · by dndungu

Implement, review, or improve WidgetKit widgets and controls. Use when building Home Screen, Lock Screen, StandBy, or CarPlay widgets with timeline providers; configurable widgets with AppIntentTimelineProvider; interactive widgets or Control Center controls with Button/Toggle wiring; WidgetKit push reloads, refresh budgets, deep links, Smart Stack relevance, Liquid Glass/accented rendering, widg…

No reviews yet
0 installs
38 views
0.0% view→install

Install

$ agentstack add skill-dndungu-agent-skills-widgetkit

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-dndungu-agent-skills-widgetkit)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Widgetkit? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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 and APNs content-state work to the sibling activitykit skill, and Siri/Shortcuts/Spotlight or entity-modeling work to the App Intents framework docs (no dedicated skill in this repo).

Sibling routing: widget VISUAL design rules (type scale, Gauge over manual drawing, containerBackground, family consistency, memory/Canvas) → /swiftui-craft; new designs and mockups → /ios-design; App Store widget review rules → /app-store; simulator capture and device management → /ios-simulator.

See [references/widgetkit-advanced.md](references/widgetkit-advanced.md) for timeline strategies, push-based updates, Xcode setup, and advanced patterns.

Contents

  • [Workflow](#workflow)
  • [Widget Protocol and WidgetBundle](#widget-protocol-and-widgetbundle)
  • [Configuration Types](#configuration-types)
  • [TimelineProvider](#timelineprovider)
  • [AppIntentTimelineProvider](#appintenttimelineprovider)
  • [Widget Families](#widget-families)
  • [Interactive Widgets (iOS 17+)](#interactive-widgets-ios-17)
  • [ActivityConfiguration Handoff](#activityconfiguration-handoff)
  • [Control Center Widgets (iOS 18+)](#control-center-widgets-ios-18)
  • [Lock Screen Widgets](#lock-screen-widgets)
  • [StandBy Mode](#standby-mode)
  • [Widget URL Handling and Deep Links](#widget-url-handling-and-deep-links)
  • [Smart Stack Relevance](#smart-stack-relevance)
  • [Design Patterns](#design-patterns)
  • [iOS 26 Additions](#ios-26-additions)
  • [Common Mistakes](#common-mistakes)
  • [Review Checklist](#review-checklist)
  • [References](#references)

Workflow

1. Create a new widget

  1. Add a Widget Extension target in Xcode (File > New > Target > Widget Extension).
  2. Enable App Groups for shared data between the app and widget extension.
  3. Define a TimelineEntry struct with a date property and display data.
  4. Implement a TimelineProvider (static) or AppIntentTimelineProvider (configurable).
  5. Build the widget view using SwiftUI, adapting layout per WidgetFamily.
  6. Declare the Widget conforming struct with a configuration and supported families.
  7. Register all widgets in a WidgetBundle annotated with @main.

2. Integrate adjacent surfaces

  1. Register an ActivityConfiguration in the widget bundle when the app has a

Live Activity, but keep ActivityAttributes, request/update/end, APNs content-state, and Dynamic Island layout depth in activitykit.

  1. Place Button, Toggle, ControlWidgetButton, and ControlWidgetToggle

in WidgetKit views or controls, but keep intent modeling, entities, queries, Siri, Shortcuts, and Spotlight in app-intents.

3. Add a Control Center control

  1. Reuse an AppIntent/OpenIntent for a button, or a SetValueIntent for a toggle.
  2. Create a ControlWidgetButton or ControlWidgetToggle in the widget bundle.
  3. Use StaticControlConfiguration or AppIntentControlConfiguration.

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.

struct OrderStatusWidget: Widget {
    let kind: String = "OrderStatusWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: OrderProvider()) { entry in
            OrderWidgetView(entry: entry)
        }
        .configurationDisplayName("Order Status")
        .description("Track your current order.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

WidgetBundle

Use WidgetBundle to expose multiple widgets from a single extension.

@main
struct MyAppWidgets: WidgetBundle {
    var body: some Widget {
        OrderStatusWidget()
        FavoritesWidget()
        DeliveryActivityWidget()   // ActivityConfiguration handoff
        QuickActionControl()       // Control Center
    }
}

Configuration Types

Use StaticConfiguration for non-configurable widgets. Use AppIntentConfiguration (recommended) for configurable widgets paired with AppIntentTimelineProvider.

// Static
StaticConfiguration(kind: "MyWidget", provider: MyProvider()) { entry in
    MyWidgetView(entry: entry)
}
// Configurable
AppIntentConfiguration(kind: "ConfigWidget", intent: SelectCategoryIntent.self,
                       provider: CategoryProvider()) { entry in
    CategoryWidgetView(entry: entry)
}

Shared Modifiers

| Modifier | Purpose | |---|---| | .configurationDisplayName(_:) | Name shown in the widget gallery | | .description(_:) | Description shown in the widget gallery | | .supportedFamilies(_:) | Array of WidgetFamily values | | .supplementalActivityFamilies(_:) | Live Activity sizes (.small, .medium) |

TimelineProvider

For static (non-configurable) widgets. Uses completion handlers. Three required methods:

struct WeatherProvider: TimelineProvider {
    typealias Entry = WeatherEntry

    func placeholder(in context: Context) -> WeatherEntry {
        WeatherEntry(date: .now, temperature: 72, condition: "Sunny")
    }

    func getSnapshot(in context: Context, completion: @escaping (WeatherEntry) -> Void) {
        let entry = context.isPreview
            ? placeholder(in: context)
            : WeatherEntry(date: .now, temperature: currentTemp, condition: currentCondition)
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) {
        Task {
            let weather = await WeatherService.shared.fetch()
            let entry = WeatherEntry(date: .now, temperature: weather.temp, condition: weather.condition)
            let nextUpdate = Calendar.current.date(byAdding: .hour, value: 1, to: .now)!
            completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
        }
    }
}

AppIntentTimelineProvider

For configurable widgets. Uses async/await natively. Receives user intent configuration.

struct CategoryProvider: AppIntentTimelineProvider {
    typealias Entry = CategoryEntry
    typealias Intent = SelectCategoryIntent

    func placeholder(in context: Context) -> CategoryEntry {
        CategoryEntry(date: .now, categoryName: "Sample", items: [])
    }

    func snapshot(for config: SelectCategoryIntent, in context: Context) async -> CategoryEntry {
        let items = await DataStore.shared.items(for: config.category)
        return CategoryEntry(date: .now, categoryName: config.category.name, items: items)
    }

    func timeline(for config: SelectCategoryIntent, in context: Context) async -> Timeline {
        let items = await DataStore.shared.items(for: config.category)
        let entry = CategoryEntry(date: .now, categoryName: config.category.name, items: items)
        return Timeline(entries: [entry], policy: .atEnd)
    }
}

Widget Families

| Family | Platform | |---|---| | .systemSmall | iOS, iPadOS, macOS, CarPlay (iOS 26+) | | .systemMedium | iOS, iPadOS, macOS | | .systemLarge | iOS, iPadOS, macOS | | .systemExtraLarge | iPadOS only | | .accessoryCircular | iOS, watchOS | | .accessoryRectangular | iOS, watchOS | | .accessoryInline | iOS, watchOS | | .accessoryCorner | watchOS only |

Adapt layout per family using @Environment(\.widgetFamily):

@Environment(\.widgetFamily) var family

var body: some View {
    switch family {
    case .systemSmall: CompactView(entry: entry)
    case .systemMedium: DetailedView(entry: entry)
    case .accessoryCircular: CircularView(entry: entry)
    default: FullView(entry: entry)
    }
}

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.

struct InteractiveWidgetView: View {
    let entry: FavoriteEntry

    var body: some View {
        Button(intent: ToggleFavoriteIntent(itemID: entry.itemID)) {
            Image(systemName: entry.isFavorite ? "star.fill" : "star")
        }
    }
}

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.

struct DeliveryActivityWidget: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: DeliveryAttributes.self) { context in
            DeliveryLiveActivityView(context: context)
        } dynamicIsland: { context in
            DeliveryDynamicIsland(context: context)
        }
    }
}

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.

struct OpenCameraControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "OpenCamera") {
            ControlWidgetButton(action: OpenCameraIntent()) {
                Label("Camera", systemImage: "camera.fill")
            }
        }
        .displayName("Open Camera")
    }
}

struct FlashlightControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "Flashlight", provider: FlashlightValueProvider()) { value in
            ControlWidgetToggle(isOn: value, action: ToggleFlashlightIntent()) {
                Label("Flashlight", systemImage: value ? "flashlight.on.fill" : "flashlight.off.fill")
            }
        }
        .displayName("Flashlight")
    }
}

Lock Screen Widgets

Use accessory families and AccessoryWidgetBackground.

struct StepsWidget: Widget {
    let kind = "StepsWidget"
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: StepsProvider()) { entry in
            ZStack {
                AccessoryWidgetBackground()
                VStack {
                    Image(systemName: "figure.walk")
                    Text("\(entry.stepCount)").font(.headline)
                }
            }
        }
        .supportedFamilies([.accessoryCircular, .accessoryRectangular, .accessoryInline])
    }
}

StandBy Mode

Small system widgets can appear in StandBy and CarPlay. Use @Environment(\.widgetLocation) for conditional rendering:

@Environment(\.widgetLocation) var location
// location == .standBy, .homeScreen, .lockScreen, .carPlay, etc.

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 Gauge over manual arcs. Use .gaugeStyle(.accessoryCircular) for

Lock Screen circular widgets and .linearCapacity for 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 Canvas for 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. Apple budgets

40–70 refreshes per day with entries at least 5 minutes apart. Use Text(timerInterval:countsDown:) for live countdowns instead of burning timeline entries.

See [references/widgetkit-advanced.md](references/widgetkit-advanced.md) 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 WidgetPushHandler type 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, and

aps.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 WidgetCenter reloads remain the fallback path.

Control push reloads:

  • Register a ControlPushHandler with .pushHandler(...) on the

ControlWidgetConfiguration.

  • pushTokensDidChange(controls:) receives [ControlInfo]; read tokens from

each control's pushInfo.

  • Use apns-push-type: controls, topic suffix .push-type.controls, and

aps.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

  1. Using IntentTimelineProvider instead of AppIntentTimelineProvider.

IntentTimelineProvider is the older SiriKit Intents-based provider. Prefer AppIntentTimelineProvider with the App Intents framework for new widgets.

  1. 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 appropriate TimelineReloadPolicy values.

  1. 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.

  1. Performing network calls in placeholder(). placeholder(in:) must return

synchronously with sample data. Use getTimeline or timeline(for:in:) for async work.

  1. Letting WidgetKit absorb sibling-skill work. Keep full Live Activity

lifecycle in activitykit and full App Intent modeling in the App Intents layer (Apple docs).

  1. Treating WidgetKit push payloads as state. Widget and control pushes are

reload signals. Persist state in shared storage or refetch it in the provider.

  1. Registering widget pushes through User Notifications. Widget push tokens

come from WidgetKit handlers, not UNUserNotificationCenter.

  1. **Putting heav

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.