-
WidgetKit foundations
Widgets highlight your app's most important content across the system, providing people with another opportunity to engage. Discover the different types of widgets and explore the qualities that make them memorable. Learn how to create widgets, keep them up to date, and offer ways for people to customize them through App Intents and dynamic styling.
Chapters
- 0:01 - Introduction
- 1:03 - Fundamentals
- 13:15 - Integrate with your app
- 17:04 - Adapt with the system
Resources
Related Videos
WWDC26
WWDC25
WWDC23
WWDC21
-
Search this video…
Hi, my name is Jonathan Long and I'm an engineer on the system experience team.
People love using widgets. Your widgets provide relevant content in the convenient places people visit most. Widgets are available on many Apple platforms including iOS, iPadOS, watchOS, visionOS, and macOS. Your widgets extend the reach of your app across the system providing another opportunity for people to experience your great apps. So let's dive into widgets and discuss the foundations of widget kit, how to build your first widget, and how to keep it up to date. Today, I will cover Widget fundamentals and building your first widget. What WidgetKit offers to better integrate your app and widget experience. And how to ensure your widgets adapt when the system environment changes, like to a tinted environment. First, fundamentals, beginning with what are widgets? There are three qualities that make widgets memorable.
Widgets are glanceable. People should be able to understand your widgets with a quick glance. For example, weather shows me just enough information about the current forecast to help me get ready for my day.
Widgets are relevant. Widgets content should match expectations for time, personal patterns, and location. The calendar widget shows me the next events at the current time and updates throughout the day. Widgets are personalizable; able to be configured with the content that matters to me, like this photos memory widget showing me a great memory at a family camp with my daughters.
Using WidgetKit and SwiftUI you can provide great glanceable, relevant, and personalizable widgets for your apps content.
I have been building an app for my book club to encourage me to read more. The app lets me track how much I have read, the books I have completed, and creates a schedule to help me complete a book before my next book club meeting. I also created some widgets so I can view this data right from my home screen.
The first widget is the reading goals widget encouraging me to read throughout the day.
The second, the reading log widget, helps me keep track of my current progress through a book. The third widget builds a schedule for me to follow so I can finish my book on time.
Throughout this talk I'll cover key decisions for each widget that can help when creating your widgets.
I'll start by covering how an app provides widgets to the system. Whether an app is built with SwiftUI or UIKit, it can have widgets, and the widgets will be built with SwiftUI. Your app provides widgets to the system from a widget extension. This extension runs as a separate process from your app. Because your widget extension is running as a separate process, you will need to use a shared container in an app group to provide data from your app to your widget extension. You can use something like a shared database or user defaults. The widget extension is solely focused on the widget. The system and widget kit interact with the extension to get the data needed for widgets.
WidgetKit asks the extension for content. This content is called a timeline.
A timeline is made up of multiple timeline entries. WidgetKit provides these entries as data for the widgets view. The resulting views are archived. The system displays them at their relevant time.
WidgetExtensions expose your widgets to the system and provide their timeline content. Timelines are a series of entries, each carrying the data needed to render your widget's view at a specific point in time.
Now let's look at the code for the reading goals widget that brings these pieces together.
When you create a new target for your widget extension, Xcode generates a widget for you.
For a simple widget this implementation already has a lot of what we need. Starting with a body providing a WidgetConfiguration.
There are two types of configurations for widgets: AppIntentConfigurations and StaticConfigurations. AppIntentConfigurations are used when your widget can be configured by the user.
My widget will be automatically configured using the current book I am reading, so I am using the static configuration which is the most simple. The static configuration needs a few parameters. The kind, which is a unique identifier for this specific type of widget.
A timeline provider that produces timeline entries for the widget. And a closure that takes a timeline entry and returns a View. From the closure you return a SwiftUI view for the provided timeline entry.
Because my book club app is built in SwiftUI, I already have a view, the DailyReadingGoalView, that is exactly what I need for my widget.
To specify the background, I am using the containerBackground modifier. This modifier identifies which view is the background of my widget.
When people using my widget customize their devices with a colored or clear tint, this allows the system to replace the background view specified with a glass material view.
The view portion of the Reading Goal widget is implemented. Let's have a closer look at what is expected of the timeline provider and how timelines keep widgets relevant.
The timeline provider supplies entries to represent three separate states for my widget: Snapshots, Placeholder, and Timeline.
A snapshot is a realistic preview of a widget. It's what people see in the widget gallery.
This is an opportunity for my widget to make a strong first impression. My app has no data before someone starts using it, so I feature a popular book, Atomic Habits, with a default message. So people can imagine my widget at its best before they ever add it to their Home Screen. A placeholder is a stand-in view the system shows when your widget doesn't have content to display yet, like the very first time it's loading your timeline. Because it needs to show up instantly, fetching a placeholder has to be synchronous. So provide a placeholder that does not need data from disk or over the network.
For the Reading Goals widget, I'm using SwiftUI's redacted modifier, which creates a simplified version of my View.
A timeline entry is what your widget shows at a specific moment in time. That could be right now, or any point in the future. Your timeline provider produces a collection of these entries, and the system renders each one at its specific time.
The timeline entry for the reading goals widget has all the information the widget needs to render this view. The motivational message, pages read, title, and cover name.
Most of this data will be the same for this widget except for the motivational message. The message updates at various times throughout the day. As I begin discussing the timelines, I'm going to only show the message that will be changing over time.
Timelines are critical to how widgets stay up to date.
Each timeline entry provides the needed data to render a widget view.
This timeline is currently showing two entries, one at 9am and one at 11:30am.
The core piece of data my reading goals widget needs is the motivational message that updates throughout the day as widget kit advances the timeline.
These timeline entries are provided to my widget to produce the relevant view for that entry at the specific time, keeping my widget relevant with fresh content.
At some point your timeline will need to be refreshed with more timeline entries. This is referred to as reloading a timeline. Timelines specify their reload behavior through a reload policy.
The reload policy can be one of three options: atEnd, afterDate, and never.
Let's discuss each policy and when it makes sense to choose which one.
The atEnd policy indicates that the timeline should be reloaded when all timeline entries have been exhausted.
Throughout the day new timeline entries will be rendered.
When the last entry has been shown at 1:00pm, the system issues a reload, asking your widget extension for more content.
Your widget extension will provide a new timeline with more timeline entries. The reading goals widget updates its motivational message at varied times throughout the day. The atEnd policy makes sense because this widget provides a series of entries where the end is not a single known time and needs to be reloaded when the timeline is exhausted.
The afterDate policy allows you to specify a specific date your widget desires a reload. These are different timeline entries powering the reading schedule widget. I have redacted the complete set of data and only include the chapter information that changes over time. Here the reading schedule widget provides two timeline entries indicating what needs to be read today and tomorrow.
The widget needs to be reloaded at the end of the day to recalculate the downstream schedule.
It uses the afterDate reload policy specifying a date at the end of the day.
Once the current time reaches this date, my extension is reloaded and asked for a new timeline.
The new timeline provides entries for Tuesday and Wednesday with the recalculated schedule. The afterDate policy is great in cases like the reading schedule widget when there is a specific time you know your widget will need to be reloaded.
And lastly, the never reload policy. As the name suggests, your widget won't reload on its own. Use this when automatic reloads don't make sense. The Reading Log widget is a good example. It only needs to refresh when someone interacts with the app or widget. When you do need to reload, you can do this with an explicit call to WidgetCenter's reload APIs or by sending a push notification.
To dig deeper into reload policies, check out "Principles of great widgets" from WWDC21.
There are a few best practices to keep in mind when considering building and reloading your timeline.
Provide multiple timeline entries whenever possible. This ensures the system always has content to show for your widget.
WidgetKit was built with all day battery life in mind. So each widget is given a budget for updates. The budget is heavily influenced by users viewing habits and is updated throughout the day.
Frequency of updates can vary and the system is smart enough to adapt reloads for your widget as it makes sense.
Know that frequent reloads while your app is in the foreground might be throttled. If your widget data may have changed a final call to reload when your app enters the background is usually a good idea.
Some content is ephemeral with a defined start and end date, needs more frequent updates, and wants alerting capabilities, like a sporting event. If this describes your content, consider building a live activity. You can learn more about Live Activities in "Live Activities essentials" from WWDC 26.
So far, all my widgets have been the same size — the system medium family.
But now that my WidgetExtension and timeline provider are wired up, a whole lineup of other widget families are available to me.
Widgets come in all different shapes and sizes. It is recommended to support as many sizes as you can so that people using your widget have choices when placing their widgets.
The system extra large portrait family was introduced in visionOS 26. New in macOS, iOS, and iPadOS 27, the system extra large portrait family is now available. This new family allows people using your widgets to have a really good look at your apps content.
The Book Club reading schedule can reuse the same data from the medium widget we have already implemented. This larger size allows me to see how far along I should be in the next couple of days.
Not all widget families make sense for your widget, and starting with a few families is most simple.
Here is my daily reading goal widget I am building. I can use the .supportedFamilies modifier, listing all the widget families my widget supports.
When adding a new widget family, I can reuse the same widget and timeline provider, and build a swiftUI view that makes sense for the new families shape and size.
I just covered the basic steps you need to build your first widget. Build a widget with a widget extension that is focused on your widget. You provide content to your widget through a timeline made up of timeline entries.
Choose a timeline reload policy that makes sense for your use case.
My app now has a great iOS widget to keep me on track for book club. And my widget is actually available in other places as well.
My iOS widget is available on CarPlay and as a remote widget on macOS.
We just discussed what you need to do to build a widget. Now let's discuss some additional options WidgetKit provides to better integrate your apps content with your widget.
You can better integrate your widget with your app by using Deep links, making your widgets configurable, and by adding interactive elements to your widgets.
The default interaction for your widget is to open your app, which is a great starting point. If your widget is showing specific content from within your app, your widget can provide a deep link directly to that content.
Let's jump back into the Reading Goals Widget code and see how to do this.
The reading goals widget shows the current book I am reading. I will provide a deep link to the book's details page so tapping the widget lands people where they expect.
I will use the widgetURL modifier specifying a deep link URL for my app to handle on launch. The URL encodes the book's ID, so the app launches directly to the book's details page.
Deep links are a great way to integrate your widget with your app, keeping people in context as they move between them. Configurable widgets are another great way to integrate your widget with your app. Making your widget configurable lets people personalize their widget with specific content from your app.
The weather widget is a great example. People can configure this widget to show conditions from a location that is important to them. I like to keep track of the weather in Yosemite, just in case I can plan a spur of the moment trip.
The reading log widget in my book club app is another example of a configurable widget. People using my app can configure this widget to track reading sessions for a specific book. Configurable widgets can be configured from wherever they are placed, like from the iOS home screen.
This is the configuration UI for my reading log widget which only has one parameter. The UI allows people using my widget to select the book they want to track. I made the most recently read book the default so people don't need to make a selection. Configurable widgets also let people add multiple widgets with different configurations. Here my home screen has three reading log widgets tracking the three different books I am reading.
When you're thinking about configurable widgets, there are a few things to keep in mind. Consider whether your widget's content should change depending on who's using it. Keep configuration fast — one or two parameters is usually all you need.
And don't require configuration up front, provide sensible defaults that people can tweak later if they want to. To learn more about making your widget configurable with App Intents, check out "Explore enhancements to App Intents" from WWDC23.
Configurable widgets give people using your widget options to make their devices relevant and personal for them.
Your widget can also integrate with your app through interactive elements. Buttons and toggles let people perform actions directly from the widget.
Reminders is a great example of an interactive widget. Once I have completed a task, I can check it off right from the widget. Receiving the dopamine hit I need to keep moving through my day.
When you're thinking about interactive elements, here are a few things to keep in mind.
Widgets can surface interactive elements as either a toggle or a button.
Think about the most important action in your app and expose it from your widget, like the complete chapter button on the reading log widget. Widget views are archived and rendered by the system, so your code isn't running while the widget is on screen. Buttons and toggles take an App Intent that the system can execute on your behalf when someone interacts with the element. To learn more about interactive widgets, check out "Bring widgets to life" from WWDC23.
Your widget is an extension of your app. Deep links, configurable widgets, and interactive elements are all great ways to unify the experience between your app and widget.
People using your widget can also customize their system experience. Widgets were designed to adapt to these system changes. On iOS, the system can be customized to be tinted with a specific color or to have a clear tint. With either customization, the system renders your widget through a glass material, tinting your content and replacing your background with an adaptive glass effect. This keeps every widget on the Home Screen feeling cohesive. SwiftUI does a lot of the heavy lifting here, but it is important to test your widget to make sure it looks great.
As I begin testing my reading goals widget, I can see that it is looking really great when rendered in full color. Before I'm done, I want to test with a tinted customization to make sure my widget is rendering as expected.
I'll do this by running on device and customizing my home screen.
When I change my iPhone to use the clear mode, my widget isn't rendering correctly. The book cover is a large white rectangle, which is not the intended behavior. This is my BookCoverImage view. In the view's body the image is rendered for this specific book from the asset catalog. When rendering in the accented rendering mode the system is unable to accent the image appropriately.
I can specify the rendering mode to be used for this image with the widgetAccentedRenderingMode modifier.
With this option my widget now renders the book cover using full color. I am using the full color option so the book covers are rendered respecting their original colors. With this change my widget is looking great in the accented rendering mode.
It's important to test your widget in every environment they appear. Start with your local devices. Try your widgets in full color, tinted, and clear mode to make sure they render as expected.
Remember that your iOS widgets also show up on macOS as remote widgets. Take the time to test that your interactions still feel right when someone's using them from a Mac.
Lean on SwiftUI previews to iterate quickly.
In the swiftUI canvas on the right side of Xcode you can check different families, color schemes, and rendering modes without ever leaving Xcode.
Turn on WidgetKit developer mode while you're testing to lift constraints like reload budgets. This allows you to iterate on your widgets more quickly. To learn more about adapting your widget to these system customizations, check out "What's new in widgets" from WWDC25.
I covered a lot today. Before I let you go, keep these things in mind.
I hope you are inspired to go and build a great widget! Consider how you can extend and personalize your widget experience by integrating with your app. And be sure to test and adapt your widget to all the custom environments that each platform provides.
Widgets make my iPhone fun and personal to me! I can't wait to use the great widgets you create!
-
-
3:50 - DailyReadingGoalWidget
struct DailyReadingGoalWidget: Widget { let kind = "DailyReadingGoalWidget" var body: some WidgetConfiguration { StaticConfiguration( kind: kind, provider: DailyReadingGoalProvider() ) { entry in DailyReadingGoalView(book: entry.book, message: entry.message, timeOfDay: entry.timeOfDay) .environment(\.colorScheme, .dark) .containerBackground(for: .widget) { Background() } } } } -
12:25 - Supported Families
struct DailyReadingGoalWidget: Widget { let kind = "DailyReadingGoalWidget" var body: some WidgetConfiguration { StaticConfiguration( kind: kind, provider: DailyReadingGoalProvider() ) { entry in DailyReadingGoalView(book: entry.book, message: entry.message, timeOfDay: entry.timeOfDay) .environment(\.colorScheme, .dark) .containerBackground(for: .widget) { Background() } } .supportedFamilies([.systemMedium]) } } -
14:03 - Adding deep links
struct DailyReadingGoalWidget: Widget { let kind = "DailyReadingGoalWidget" var body: some WidgetConfiguration { StaticConfiguration( kind: kind, provider: DailyReadingGoalProvider() ) { entry in DailyReadingGoalView(book: entry.book, message: entry.message, timeOfDay: entry.timeOfDay) .environment(\.colorScheme, .dark) .containerBackground(for: .widget) { Background() } .widgetURL(URL(string: "bookclub://reading/\(book.bookID)")) } .supportedFamilies([.systemMedium]) } } -
18:17 - Accented rendering mode
struct BookCoverImage: View { let imageName: String var body: some View { Image(imageName: bundle: .main) .widgetAccentedRenderingMode(.fullColor) } }
-
-
- 0:01 - Introduction
Widgets highlight your app's most important content across the system. The best widgets are glanceable, relevant, and personalizable. Learn how to build your first widget and keep it up to date, extending the reach of your app across platforms with WidgetKit and SwiftUI.
- 1:03 - Fundamentals
Widgets should be glanceable, relevant, and personalizable. They are built by creating a widget extension that exposes a timeline of TimelineEntry values. Each TimelineEntry provides the data to render a SwiftUI view at a particular moment in time. Learn how to define a widget with a StaticConfiguration or AppIntentConfiguration, build a quality TimelineProvider, and select a timeline reload policy to keep your widget up to date. Discover the various sizes and placements for widgets with .supportedFamilies — including the new systemExtraLargePortrait family coming to macOS, iOS, and iPadOS 27.
- 13:15 - Integrate with your app
WidgetKit offers three key integration points to tighten the connection between a widget and your app. Deep links route taps directly to specific content in your app. Configurable widgets let people personalize widget content. Interactive elements that let people perform the most important actions from within your app using App Intents.
- 17:04 - Adapt with the system
Widgets are dynamic and adapt with the system appearance modes like full color, tinted, and clear. SwiftUI handles most of the adaptation automatically, though you can customize the behavior of particular Views with the .widgetAccentedRenderingMode(.fullColor) modifier. Learn techniques to test your widgets for considerations with appearance modes and budgeted reloads.