Building configurable widgets with WidgetKit and SwiftUI
Learn how to let users customize a WidgetKit widget with App Intents.
updated on 20 Jul 2026 · 5 min read
In the previous article on building widgets with WidgetKit and SwiftUI, we looked at the basic structure of a WidgetKit widget.
In this article, we'll look at configurable widgets. A configurable widget lets users choose values that affect what the widget displays. For example, a finance app could let users choose an account, a task app could let users choose a project, and a quote app could let users choose a category.
We'll use a simple quote widget as an example. The user will be able to choose a quote category when adding or editing the widget, and the widget will show a quote for that selected category.
The flow is: the user chooses a value, WidgetKit stores it in the intent, the provider receives the intent, and the widget renders an entry built from that value.

Defining the configurable value
The first step is to define the value the user can choose. In our example, this value is the quote category.
For a fixed list of options, we can use an enum that conforms to AppEnum. Dynamic options are also possible when the available values come from app data, and we'll look at that later in the article.
import AppIntentsenum QuoteCategory: String, AppEnum {case motivationcase focuscase mindfulnessstatic var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Category")static var caseDisplayRepresentations: [QuoteCategory: DisplayRepresentation] = [.motivation: "Motivation",.focus: "Focus",.mindfulness: "Mindfulness"]}
The display representations define how the enum cases appear in the widget configuration UI.
Exposing the value to WidgetKit
Next, we define a type that conforms to WidgetConfigurationIntent. The intent describes the values WidgetKit should show when the user configures the widget.
We can use the QuoteCategory enum as a parameter:
struct QuoteWidgetIntent: WidgetConfigurationIntent {static var title: LocalizedStringResource = "Quote Widget"static var description = IntentDescription("Shows a quote from the selected category.")@Parameter(title: "Category")var category: QuoteCategory = .motivation}
The @Parameter property is the value users can change. Here, the user can choose a quote category, and the widget can later use that selected category to decide which quote to display.
Connecting the intent to the widget
For a configurable widget, we use AppIntentConfiguration:
import AppIntentsimport SwiftUIimport WidgetKitstruct QuoteWidget: Widget {var body: some WidgetConfiguration {AppIntentConfiguration(kind: "com.example.QuoteWidget",intent: QuoteWidgetIntent.self,provider: QuoteTimelineProvider()) { entry inQuoteWidgetView(entry: entry)}.configurationDisplayName("Quote").description("Shows a quote for the selected category.").supportedFamilies([.systemSmall, .systemMedium])}}
WidgetKit uses the intent type to show the widget configuration UI and to pass the selected values to the timeline provider.
Using the selected value
A timeline entry contains the data that the widget view needs to render a specific state:
struct QuoteEntry: TimelineEntry {let date: Datelet category: QuoteCategorylet quote: String}
Because the widget is configurable, the provider conforms to AppIntentTimelineProvider. Its snapshot and timeline methods receive the selected configuration, which is where the user choice becomes useful:
struct QuoteTimelineProvider: AppIntentTimelineProvider {func placeholder(in context: Context) -> QuoteEntry {QuoteEntry(date: Date(),category: .motivation,quote: "Small steps count.")}func snapshot(for configuration: QuoteWidgetIntent, in context: Context) async -> QuoteEntry {entry(for: configuration.category)}func timeline(for configuration: QuoteWidgetIntent, in context: Context) async -> Timeline<QuoteEntry> {let entry = entry(for: configuration.category)let nextUpdate = Date().addingTimeInterval(60 * 60)return Timeline(entries: [entry], policy: .after(nextUpdate))}private func entry(for category: QuoteCategory) -> QuoteEntry {QuoteEntry(date: Date(),category: category,quote: quote(for: category))}private func quote(for category: QuoteCategory) -> String {switch category {case .motivation:return "Small steps count."case .focus:return "One thing at a time."case .mindfulness:return "Take a breath before moving on."}}}
The important part is configuration.category. It contains the category selected by the user, so the provider can create a timeline entry for that category.
Building the widget view
The widget view receives the timeline entry and renders the selected content:
struct QuoteWidgetView: View {let entry: QuoteEntryvar body: some View {Text(entry.quote).font(.headline).containerBackground(.background, for: .widget)}}
In a real app, the provider could load the quote from shared app data, a local database, or another persistence layer that the app and widget extension can both access.
When options come from app data
An enum works well when the available choices are known ahead of time. If the options come from user data, such as projects, accounts, or lists, the configuration can use App Intents entities and queries instead.
For example, a task app might let users choose one of their own projects. In that case, the widget configuration needs to load the available projects dynamically instead of defining them as enum cases.
Summary
To build a configurable widget, we define a WidgetConfigurationIntent, use it with AppIntentConfiguration, and read the selected values in an AppIntentTimelineProvider.
For simple fixed choices, an AppEnum is often enough. For user-generated choices, App Intents entities are a better fit because the available options can be loaded dynamically.



Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips



