Widgets That Work in Real Life

Many widgets look polished but fail in daily use because they overload information and refresh too infrequently.

A better mental model:

  • One primary signal (status, next action, or trend)
  • One high-value action via App Intent
  • Predictable timeline refresh

Choosing the Right Widget Type

Widget TypeBest ForCommon Mistake
SmallSingle KPI or next taskToo much text
MediumSummary + one actionMultiple competing CTAs
Lock ScreenReal-time-ish glanceDense visuals on tiny space

Start from user context, not design preferences.

App Intents: Keep Actions Focused

Use App Intents for actions that are:

  1. Safe to run quickly
  2. Understandable without opening the app
  3. Valuable from home/lock screen

Great examples include โ€œMark done,โ€ โ€œStart timer,โ€ and โ€œLog water.โ€

import AppIntents

struct CompleteTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Complete Task"
    static var description = IntentDescription("Mark a task as completed from the widget.")

    @Parameter(title: "Task ID")
    var taskID: String

    func perform() async throws -> some IntentResult {
        try await TaskService.shared.complete(taskID: taskID)
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
        return .result()
    }
}

Timeline Strategy: Fresh Enough, Not Wasteful

Three rules keep your widget healthy:

  • Refresh on meaningful data changes
  • Precompute upcoming entries when schedule is predictable
  • Avoid minute-level refresh unless product value clearly justifies it

This improves battery behavior while keeping content useful.

import WidgetKit

struct TaskEntry: TimelineEntry {
    let date: Date
    let title: String
    let remainingCount: Int
}

struct TaskProvider: TimelineProvider {
    func placeholder(in context: Context) -> TaskEntry {
        TaskEntry(date: .now, title: "Daily Plan", remainingCount: 3)
    }

    func getSnapshot(in context: Context, completion: @escaping (TaskEntry) -> Void) {
        completion(TaskEntry(date: .now, title: "Daily Plan", remainingCount: 3))
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<TaskEntry>) -> Void) {
        let now = Date()
        let entry = TaskEntry(date: now, title: "Daily Plan", remainingCount: TaskService.shared.remainingToday())
        let refresh = Calendar.current.date(byAdding: .minute, value: 30, to: now) ?? now.addingTimeInterval(1800)
        completion(Timeline(entries: [entry], policy: .after(refresh)))
    }
}

Complete Widget Example

import SwiftUI
import WidgetKit

struct TaskWidgetEntryView: View {
    var entry: TaskProvider.Entry

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(entry.title).font(.headline)
            Text("\(entry.remainingCount) tasks left").font(.subheadline)
            Button(intent: CompleteTaskIntent(taskID: "next-task-id")) {
                Text("Mark next done")
            }
            .buttonStyle(.borderedProminent)
        }
        .containerBackground(.fill.tertiary, for: .widget)
    }
}

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

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: TaskProvider()) { entry in
            TaskWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("Today Tasks")
        .description("See your next task and complete it quickly.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

UX Patterns That Increase Engagement

  • Show relative time (โ€œupdated 12m agoโ€) to build trust
  • Use clear empty states (โ€œNo tasks due todayโ€)
  • Keep tap targets intentional and limited
  • Mirror app terminology so users feel continuity

Consistency beats novelty for recurring widgets.

Shipping Checklist โœ…

  • Widget communicates one primary idea in <2 seconds
  • App Intent action completes without confusion
  • Timeline refresh policy is documented
  • Empty/error states are designed, not default
  • Accessibility labels are present and meaningful

Final Thought

Useful widgets remove steps from a habit. If users can glance, act, and continue their day, your widget is doing its job.