Skip to content

Design ​

Widget Layout ​

Widget Size ​

Size is the first layout constraint. Widgets are much smaller than app screens, so the view must be designed for the widget's actual dimensions. Choose text sizes based on those dimensions and verify that required information remains legible.

Widget views are responsive, but most widgets do not need to support every size variant. Use widgetFamilies in Await.define to declare the sizes the widget supports.

Widget size values use iOS logical points pt.

Use the runtime size parameter as the source of truth for layout. Both widget() and widgetTimeline() receive parameters that include the widget size.

Use the numbers below for rough planning, not as layout constants. Actual dimensions may vary by device, OS version, and preview context.

Common Layouts ​

Common widget families follow a unit-and-spacing grid. As a rough reference, unit is about 170 pt and spacing is about 20 pt.

SizeGridApprox. dimensionsNotes
small1 x 1170 x 170 pt-
medium2 x 1360 x 170 pt-
large2 x 2360 x 360 pt-
extraLargePortrait2 x 3360 x 550 ptRequires iOS 27 or later
extraLarge4 x 2740 x 360 ptiPad and Mac only

The rough dimensions come from this grid formula:

text
width  = columns x unit + (columns - 1) x spacing
height = rows    x unit + (rows    - 1) x spacing

Each additional row or column adds one unit and one spacing, except for the first unit.

Layout Across Families ​

The unit-and-spacing grid can guide related layouts across widget sizes, but each size can also use a separate layout. When two sizes share a width or height, you can keep the primary content consistent and use the additional space for secondary content if it fits.

  • small and medium share height. medium adds roughly one width unit. If both show the same content, small can show the most important subset.
  • medium and large share width. large adds roughly one height unit, which can hold secondary content below the content shared with medium.
  • small and large are more loosely related. Prefer shared components and a consistent content model over literal layout reuse.
  • extraLargePortrait and extraLarge continue the same idea with even less obligation to mirror smaller layouts.

Special Layouts ​

These layouts do not follow the unit-and-spacing grid.

SizeDescriptionApprox. dimensions
accessoryInlineLock Screen inline text342 x 36 pt
accessoryCircularLock Screen circular widget58 x 58 pt
accessoryRectangularLock Screen rectangular widget148 x 58 pt
fullscreenFull-screen preview inside the Await app-

For accessoryInline, only the first Text is rendered.

Layout Rules ​

Fill the Root View ​

The widget size represents the available layout space for the widget itself.

Unless you intentionally want transparent margins around the widget, the root view returned by widget() should fill the entire widget area.

For example:

tsx
<ZStack maxSides background={color} />

or

tsx
<ZStack>
  <Color value={color} />
</ZStack>

or

tsx
<ZStack>
  <Image url={image} resizable aspectRatio='fill' />
</ZStack>

Since the widget size is available as the size parameter, you can also write:

tsx
<ZStack frame={size} background={color} />

This also fills the entire widget layout.

Widget Corners ​

Widgets are rounded rectangles. The exact corner radius can vary by system version and device. As a reference, widget corner radius on iOS 26/27 is 86 / 3 pt.

For an interior rounded view inset by padding, a concentric corner radius is:

tsx
const cornerRadius = 86 / 3 - padding;

Stack Views ​

VStack, HStack, and ZStack center their children as a group by default.

Use the alignment prop to control alignment between children. Use the alignment value in the frame modifier to position the whole child group inside its container:

tsx
frame={{width: ..., height: ..., alignment: ...}}

Use offset for displacement, not for layout alignment.

Avoid placing offset and scaleEffect on the same view layer, because that makes layout mistakes more likely.

Aspect Ratio ​

Modifier order matters when fitting or filling an image within a frame. Put aspectRatio before frame, then add clipped to prevent image overflow:

tsx
<Image url={image} resizable aspectRatio='fill' frame={{width: 120, height: 80}} clipped />

Do not put the target frame before aspectRatio:

tsx
<Image url={image} resizable frame={{width: 120, height: 80}} aspectRatio='fill' clipped />

When an image view is set to resizable, it automatically fills all available space. For setting a background image, use the following syntax:

tsx
<Image url={image} resizable aspectRatio='fill' />

Legibility And Content ​

Keep enough contrast between informational text or graphics and their background for users to read them at the actual widget size. Check text and controls in the captured preview, not only in source code.

When content benefits from hierarchy, use distinct text sizes and weights for primary and secondary information. Do not use fontWidth as a substitute for measuring or fitting text. For constrained informational text that must remain visible, minimumScaleFactor={0.1} or minimumScaleFactor={1 / fontSize} lets the system shrink it when space is tight; inspect the resulting size for legibility.

Use primary copy to explain what the widget shows, what state it is in, or what the user can do. Secondary copy can add context; microcopy can carry compact ranges or labels. Users usually know which widget they added, so add a title only when it clarifies required content.

If an emoji conveys a state, category, or value, treat it as content and check that its meaning remains clear at the rendered size.

Implementation Notes ​

Animation Stability ​

Widget views render from entry content. WidgetKit compares the view trees of successive entries. A view can interpolate property changes only if it stays at the same position in both trees. Its id must also stay the same; two empty id values satisfy this condition. Otherwise, WidgetKit treats the old and new views as unrelated.

Buttons ​

Use FullButton to quickly create a transparent tappable area that fills the available space.

Use Button when it has a child view to display. Use a custom buttonStyle to define its normal and press states. See the Widget Button guide for setup and platform constraints.