iOS 27 and macOS 27 introduce the systemExtraLargePortrait widget family, previously only available on visionOS 26, giving widgets a tall, portrait-oriented canvas to display richer app content. Developers can now opt into this new size alongside existing families to give users a more immersive glanceable experience.
โข systemExtraLargePortrait was previously only available on visionOS 26; iOS 27 and macOS 27 now expose the same family on iPhone, iPad, and Mac
โข Developers must add the new case to their .supportedFamilies modifier to opt in โ no existing widget breaks
โข The family shares the same timeline provider contract as other families, requiring only an additional SwiftUI view branch
โข The systemExtraLargePortrait family lands on iPhone, iPad, and Mac in iOS/macOS 27, letting you surface significantly more content โ like multi-day reading schedules or detailed dashboards โ without requiring a separate widget implementation
โข Reuses your existing timeline provider and entry model, so adoption is as simple as adding a new SwiftUI view branch and listing the family in .supportedFamilies
โข Gives power users a compelling reason to dedicate more home-screen real estate to your app, increasing engagement and visibility
Shows a multi-day reading schedule widget that adopts the new systemExtraLargePortrait family on iOS 27, rendering additional schedule rows that were hidden in the medium layout.
import WidgetKitimport SwiftUIโ// Pre-iOS 27: systemExtraLargePortrait was unavailable on iOS/macOS.โ// Developers could only go up to systemExtraLarge (landscape) on iPadโ// or systemLarge on iPhone. The portrait extra-large canvas did not exist.+// MARK: - Timeline Entry+struct ReadingScheduleEntry: TimelineEntry {+ let date: Date+ let days: [DaySchedule]+}โstruct ReadingScheduleWidget_Legacy: Widget {+struct DaySchedule: Identifiable {+ let id = UUID()+ let label: String // e.g. "Monday"+ let chapter: String+ let pages: String+}++// MARK: - Provider+struct ReadingScheduleProvider: TimelineProvider {+ func placeholder(in context: Context) -> ReadingScheduleEntry {+ ReadingScheduleEntry(date: .now, days: Self.sampleDays)+ }++ func getSnapshot(in context: Context,+ completion: @escaping (ReadingScheduleEntry) -> Void) {+ completion(ReadingScheduleEntry(date: .now, days: Self.sampleDays))+ }++ func getTimeline(in context: Context,+ completion: @escaping (Timeline<ReadingScheduleEntry>) -> Void) {+ let entry = ReadingScheduleEntry(date: .now, days: Self.sampleDays)+ // Reload at end of day to recalculate the downstream schedule+ let midnight = Calendar.current.startOfDay(+ for: Calendar.current.date(byAdding: .day, value: 1, to: .now)!)+ let timeline = Timeline(entries: [entry], policy: .after(midnight))+ completion(timeline)+ }++ static let sampleDays: [DaySchedule] = [+ DaySchedule(label: "Today", chapter: "Ch. 7", pages: "pp. 112โ135"),+ DaySchedule(label: "Tuesday", chapter: "Ch. 8", pages: "pp. 136โ158"),+ DaySchedule(label: "Wednesday", chapter: "Ch. 9", pages: "pp. 159โ180"),+ DaySchedule(label: "Thursday", chapter: "Ch. 10", pages: "pp. 181โ210"),+ DaySchedule(label: "Friday", chapter: "Ch. 11", pages: "pp. 211โ240"),+ ]+}++// MARK: - Views+struct ReadingScheduleMediumView: View {+ let entry: ReadingScheduleEntry+ var body: some View {+ VStack(alignment: .leading, spacing: 6) {+ Text("Reading Schedule").font(.headline)+ ForEach(entry.days.prefix(2)) { day in+ HStack {+ Text(day.label).bold().frame(width: 80, alignment: .leading)+ Text(day.chapter)+ Spacer()+ Text(day.pages).foregroundStyle(.secondary)+ }+ .font(.caption)+ }+ }+ .padding()+ }+}++// New iOS 27 view branch for the extra-large portrait canvas+struct ReadingScheduleExtraLargePortraitView: View {+ let entry: ReadingScheduleEntry+ var body: some View {+ VStack(alignment: .leading, spacing: 10) {+ Text("๐ Reading Schedule")+ .font(.title2.bold())+ Divider()+ ForEach(entry.days) { day in // Show all 5 days โ room to breathe+ HStack {+ VStack(alignment: .leading) {+ Text(day.label).font(.headline)+ Text(day.chapter).font(.subheadline).foregroundStyle(.secondary)+ }+ Spacer()+ Text(day.pages)+ .font(.callout)+ .foregroundStyle(.secondary)+ }+ .padding(.vertical, 4)+ Divider()+ }+ Spacer()+ }+ .padding()+ }+}++// MARK: - Widget Entry View+struct ReadingScheduleEntryView: View {+ @Environment(\.widgetFamily) var family+ let entry: ReadingScheduleEntry++ var body: some View {+ switch family {+ case .systemExtraLargePortrait: // New in iOS 27+ ReadingScheduleExtraLargePortraitView(entry: entry)+ default:+ ReadingScheduleMediumView(entry: entry)+ }+ }+}++// MARK: - Widget Definition+struct ReadingScheduleWidget: Widget {let kind = "ReadingScheduleWidget"var body: some WidgetConfiguration {StaticConfiguration(kind: kind, provider: ReadingScheduleProvider()) { entry inโ ReadingScheduleMediumView(entry: entry)+ ReadingScheduleEntryView(entry: entry).containerBackground(.fill.tertiary, for: .widget)}.configurationDisplayName("Reading Schedule").description("Stay on track with your book club schedule.")โ // .systemExtraLargePortrait did not exist โ max was .systemExtraLarge (iPad only)โ .supportedFamilies([.systemMedium, .systemLarge, .systemExtraLarge])+ .supportedFamilies([.systemMedium, .systemLarge, .systemExtraLargePortrait])+ // ^^^ New iOS 27 family}}
You must explicitly add .systemExtraLargePortrait to .supportedFamilies โ widgets do not automatically opt in. Build a dedicated SwiftUI view for this family rather than stretching your medium or large layout, or the result will look sparse. Budget your timeline reloads carefully; a richer view does not change the per-widget reload budget.
No special hardware required; the family was previously visionOS-only and is now broadly available across iPhone, iPad, and Mac running iOS/macOS 27
More iOS 27 APIs land every week.
Get notified when new capabilities are published โ no noise, just signal.