---
title: "ShelfWidgetContext, DroppyKit"
description: "Context passed to widget view factories on every mount."
canonical: "https://getdroppy.app/docs/droppykit/api/shelf-widget-context"
last-updated: "2026-09-09"
---

# ShelfWidgetContext

Context passed to widget view factories on every mount.

`struct ShelfWidgetContext`

## Initializers

### init(availableSize:isPaired:isShelfTransitioning:isPreview:surface:)

`init(availableSize: CGSize, isPaired: Bool = false, isShelfTransitioning: Bool = false, isPreview: Bool = false, surface: DropletSurfaceKind = .builtInNotch)`

Creates a context. Constructed by hosts, read by droplets.

### init(availableSize:isPaired:placement:isCompact:isShelfTransitioning:isPreview:surface:usesIslandCurvature:usesAdaptiveForegrounds:)

`init(availableSize: CGSize, isPaired: Bool, placement: ShelfWidgetPlacement, isCompact: Bool, isShelfTransitioning: Bool, isPreview: Bool, surface: DropletSurfaceKind, usesIslandCurvature: Bool, usesAdaptiveForegrounds: Bool)`

Creates a context with the complete host-owned presentation facts. The legacy initializer above remains intact for already compiled droplets and older source clients.

### init(availableSize:isPaired:placement:isCompact:isShelfTransitioning:isPreview:surface:usesIslandCurvature:usesAdaptiveForegrounds:notchWrap:)

`init(availableSize: CGSize, isPaired: Bool, placement: ShelfWidgetPlacement, isCompact: Bool, isShelfTransitioning: Bool, isPreview: Bool, surface: DropletSurfaceKind, usesIslandCurvature: Bool, usesAdaptiveForegrounds: Bool, notchWrap: ShelfWidgetNotchWrap?)`

Creates a context that can carry the notch wrap (1.21.0). The same facts as the initializer above, plus `notchWrap`.

## Properties

### availableSize

`let availableSize: CGSize`

The host-owned layout size for this widget mount.

A shelf host MUST provide its settled slot width in `width` before it calls [`makeWidgetView(_:context:)`](https://getdroppy.app/docs/droppykit/api/shelf-widget-providing#make-widget-view-context). A zero height means this host has no single stable height for the mount; widgets use their declared [`contentHeight`](https://getdroppy.app/docs/droppykit/api/shelf-widget-layout-traits#content-height) rather than infer height from an animated frame.

### contentInsets

`var contentInsets: EdgeInsets { get }`

The inset to apply once, at the widget's root: the clearance for the rounded corner the host clips this rectangle with.

**8 alone under a notch, 0 for a solo island card, 0 in a grouped row and 0 while wrapped.** The host clips a solo card under a notch at a continuous 18pt corner ([`shelfWidgetCornerRadius`](https://getdroppy.app/docs/droppykit/api/droppy-shell-metrics#shelf-widget-corner-radius)). Measured on SwiftUI's own continuous path, a square-cornered glyph in that corner needs 5.25pt on both axes to clear it at every card size, so this is 8 ([`sm`](https://getdroppy.app/docs/droppykit/api/droppy-spacing#sm)) on every edge there, 2.75pt more than it needs. A solo card filling the Dynamic Island (`usesIslandCurvature`) is not clipped at a card corner at all: the island shell clips it and keeps one even gap around every widget on it, Droppy's and yours, so this is zero there and the widget draws flush to its rectangle.

Droppy 16.0.0 and earlier returned zero under the 18pt corner, where a header's symbol at the top-leading corner lost its corner to the clip, and 12 on a solo island card, which the card then clipped at 34pt. The number comes from the Droppy the droplet runs in, so a droplet gets it without a rebuild.

The shelf's chrome outside the rectangle (the side gutters, the cutout and the 1.5pt slot inset) is still the host's: never pad for it, and never add a spacing token or a corner inset of your own on top of this, which doubles the gap.

Alone under a notch it is not how Droppy's own widgets are laid out. They draw flush to the rectangle and clear the corner element by element (a header inset by about 11pt and 8pt, row cards rounder than the clip), so a solo droplet's content sits 8pt in from the edge. One uniform number is what lets every droplet clear a clip it cannot see.

**0 on every edge in a grouped row** (1.21.0, `isPaired`). Two to four widgets in a row share one alignment: every card's first row starts on the row's top line (its top edge), its last row ends on the bottom line (its bottom edge, 17.5pt over the shell's floor), and its sides are the gaps the shelf keeps to the walls and to the divider. Droppy's own widgets draw flush to those edges, so the host clips a grouped card square and hands it no inset: a droplet's rows land on the same lines as the built-ins' beside it, where an 8pt inset left its header 8pt under theirs and its last row 8pt over. The rectangle is the declared height less the 16pt its insets would have taken, so the content keeps exactly the room it had. A row is as tall as its tallest widget, so yours may be given more than it declared: keep the top row on the top edge, the last row on the bottom edge, and let one `Spacer(minLength: 0)` between them take the rest.

**0 on every edge when `notchWrap` is set** (1.21.0). A wrapped card is clipped square, like Droppy's own wrapped cards (Notes, Pomodoro, the Calendar), so no corner arc can shave a control, and the card's edges are the 17.5pt the shelf keeps from its walls: the row beside the camera starts at the card's top edge and its leading and trailing controls sit flush on its sides, exactly where the built-ins' do. Artwork that fills a wrapped card rounds itself, as on a solo island card.

A declared [`fixed(_:)`](https://getdroppy.app/docs/droppykit/api/shelf-widget-content-height#fixed) or dynamic height is the whole rectangle, this inset included: budget 16pt of it under the 18pt corner; a solo island card spends none, and a grouped row is handed the height less those 16pt.

Artwork that fills a grouped or wrapped card rounds itself at [`shelfWidgetCornerRadius`](https://getdroppy.app/docs/droppykit/api/droppy-shell-metrics#shelf-widget-corner-radius), since that card is clipped square.

Apply it as one `.padding(context.contentInsets)` at the root:

```
VStack(alignment: .leading, spacing: DroppySpacing.smd) {
    header
    body
}
.padding(context.contentInsets)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
```

A widget's root gets no background, fill, outline or rounded box of its own (see [Design guidelines](https://getdroppy.app/docs/droppykit/design)), and a fill drawn to hold ordinary content is a card, not an exception. The one narrow exception is artwork that IS the widget's content, a picture filling the whole tile the way Droppy's own Weather card is painted with its condition sky. That artwork shows the tile's edge, so the content over it pads [`mdl`](https://getdroppy.app/docs/droppykit/api/droppy-spacing#mdl) on every edge in place of this, and the artwork goes after the padding so it still reaches the rounded edge:

```
content
    .padding(DroppySpacing.mdl)
    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
    .background { artwork }
```

On a solo island card no card corner clips the artwork, so round it yourself there, at [`shelfFullBleedCornerRadius`](https://getdroppy.app/docs/droppykit/api/droppy-shell-metrics#shelf-full-bleed-corner-radius), the way the Weather card rounds its sky.

### isCompact

`let isCompact: Bool`

Whether the host requested the widget's compact composition.

Every grouped row (`2...4` widgets) is compact. A host may also choose a compact composition for a solo variant, but it must never hand a grouped widget a full-density context; use `placement` for the leading, middle, or trailing distinction within that grouped row.

### isPaired

`let isPaired: Bool`

True when the widget shares its row with one to three other widgets. This compatibility property is true for every grouped row (`2...4`). Use `placement` to distinguish leading, middle, and trailing slots, and `isCompact` to select the grouped visual composition.

### isPreview

`let isPreview: Bool`

True when the view is mounted in a non-interactive preview (settings mockups, the harness screenshot mode). Skip side effects.

### isShelfTransitioning

`let isShelfTransitioning: Bool`

True while the shelf itself is animating open or closed. Freeze expensive live content while this is true; the shelf's springs must never compete with widget-internal animation.

### notchWrap

`let notchWrap: ShelfWidgetNotchWrap?`

The bands beside the camera when the host wrapped this widget around the notch, or `nil` for the classic layout. Only ever set for a widget that opted in through [`notchWrap`](https://getdroppy.app/docs/droppykit/api/shelf-widget-layout-traits#notch-wrap), alone on the shelf of a Mac with a notch. See [`ShelfWidgetNotchWrap`](https://getdroppy.app/docs/droppykit/api/shelf-widget-notch-wrap). Added in 1.21.0; contexts built with the older initializers carry `nil`.

### placement

`let placement: ShelfWidgetPlacement`

The widget's exact row placement. In a three-or-four-widget row, an inner card receives `.middle`; use this rather than treating every grouped widget as a leading or trailing peer.

### surface

`let surface: DropletSurfaceKind`

The surface kind hosting the widget.

### usesAdaptiveForegrounds

`let usesAdaptiveForegrounds: Bool`

Whether the shelf is drawn over a transparent notch material and the product should select adaptive foreground colors.

### usesIslandCurvature

`let usesIslandCurvature: Bool`

Whether the card is a solo card filling the Dynamic Island, the one mount the host does not clip at a card corner: the island shell is its clip. A grouped row on the island, and every card under a notch, is clipped at the 18pt card corner. Products use this only for internal edge clearance; the host remains the owner of the outer clip.
