All posts

Which surface, and how to make it look like part of Droppy

Building a droplet is the easy half. The half that decides whether a submission comes back for design is fitting Droppy's surfaces, which are dark, dense and small. Two mistakes recur: reaching for a surface bigger than the job needs, and padding for the camera housing the host already cleared.

Direct answer: start with a shelf widget for controls the user should be able to reach even when no activity is running. Add a live activity for glanceable status, a HUD for a brief notification or interaction, and an expanded surface when the task needs more room. Keep the host's layout and motion in charge around your content.

Version scope: these are the DroppyKit 1.8.1 guidelines and the host surfaces documented for Droppy 15.3. The current scaffold requires that host version. Older public builds can refuse a bundle or lack a surface, so check the host support guide and the compatibility message before assuming a preview is available in your installed app.

Cobalt engraved illustration of a notched laptop beside three floating panels of increasing size
Every surface is measured from the widget's own rectangle outward; the host owns the chrome beyond it.

Choosing the surface

SurfaceReach for it whenControlsLifetime
Shelf widgetThere is something to look at, change or configureYesWhile the shelf is open
Live activityA status a glance should answerIn the rowWhile the work runs
Mini HUDA short confirmation or a transient progress stepIn a cardSeconds
Expanded surfaceThe task needs the whole shelf: a picker, a form, a detail viewYesUntil dismissed

A shelf widget is a useful default because it can remain available when there is no running activity. Hovering or clicking a live activity opens the shelf, and the compact row hides while the shelf is open. Put essential Start and Stop controls in the widget so they remain reachable through that transition. Settings panes and expanded surfaces can also hold controls; the choice depends on the task.

A shelf widget is a rectangle you declare

A descriptor carries an id, a title, a symbol and layout traits, and the traits are the widget's own rectangle, not the shell around it. preferredSoloWidth and preferredPairedWidth are both required: they are typed optional for binary compatibility only, and a current host refuses a descriptor that omits either. That rectangle is what your view draws in and what context.availableSize reports back.

The shelf adds its own chrome around it, including the cutout clearance on a notch, which is also the answer to the most common layout mistake. context.contentInsets is the host's number for this slot, and under a notch it is zero, because the chrome has already inset the rectangle: a widget that pads again sits lower and narrower than the built-in beside it. Lay out to context.availableSize.width, not to the number you declared.

Solo and paired are different compositions. Paired, the widget shares a row with up to three others and has room for one number; solo, it has the wide canvas with room for a row per item. Switch on context.isCompact, never on a width comparison, because the same width means different things on a notch and on an island.

The layout every Droppy widget shares

Fill the rectangle with a full-size frame aligned top leading, pad once at the root with context.contentInsets, and draw no card: the shelf's black is the background, Droppy paints nothing behind a widget, and a fill or outline around the whole thing is what makes a droplet look foreign. Titles go at the leading edge and values at the trailing edge, with monospacedDigit() on anything that ticks. The header is a 12pt medium symbol and a 12pt semibold title, everything steps on the 4pt grid, and every radius uses a continuous corner style. Buttons are one control: glass, in the SDK's circle style for an icon-only action, its quiet style for a labelled secondary action and its accent style for the single primary one. Drawing your own flat wash or bordered chip beside a glass button is the second most common reason a submission comes back.

The compact row is status, and one droplet holds it

A live activity publishes a state with a priority, and the host decides who wins the compact seat. Droppy's own activities arbitrate among themselves and do not compete with a droplet's: a droplet's row holds the seat when none of them want the notch, stands down the instant one does, and among droplets the highest priority wins.

Publish one stable state with a fixed priority and a fixed isInteractive, letting only the accessibility title tick, because flapping either flag resizes the shell and drops hover. And publish nil the moment the work ends, since holding the seat with nothing to say is what gets a droplet switched off.

Losing the seat is normal, and the host tells you why: idle, outranked when another droplet's priority won, surfaceSuppressed while Droppy's own surfaces own the notch. Interactive compact rows can have controls, using DroppyLiveActivityControlStyle, but the separate makeExpanded(context:) activity card is not mounted by the current host. Do not put your only controls in that card. External droplets also do not receive the detached companion-pill seat.

Use monospacedDigit() in a ticking wing. The host handles entrance and exit motion, so do not add a second entrance transition to the views it presents. For content that changes inside an already visible surface, the SDK provides DroppyTransition; using it requires a declared minimum API of 1.8.0.

The HUD has two shapes, one split by the camera

A HUD is a strip or a card. The strip is the at-rest shape, the width of the closed notch plus a wing either side, with clicks falling through to what is underneath. A card is the interactive shape Droppy's low-battery and AirPods HUDs grow into. They are the same HUD: re-present with the same id and the expanded flag and the host morphs one into the other on its own spring.

The strip is the one surface where the camera housing is still in the middle of your view, because the host cannot split a single view into wings. Lay content at the two outer edges and nothing in the centre, the way Droppy's own caps-lock and Wi-Fi strips are, and never centre it.

Takeovers, and how much chrome to switch off

An expanded surface replaces the shelf's normal contents and requires the expanded surface capability. While it is up the user cannot see anything else Droppy would have shown, so ask to suppress the least you need: the widget row, the favourites strip, the navigation lane, notification banners, auto-collapse, outside-click dismissal or swipe gestures. Suppressing outside-click dismissal means the obvious way out stops working, so keep it for a task where a stray click really would lose work.

Sizes you return are the content area, and the host adds the shelf's chrome around it; the proposal carries a standard size, a maximum, the surface kind and the display. Presentations are identified by the presentation rather than the surface id, because teardown runs after the close spring settles.

A notch, an island, and a refusal

On a notched MacBook, shelf widgets receive a rectangle already clear of the camera housing. Compact wings and HUD strips have different geometry, so follow the context for the particular surface instead of applying one global top inset. On a display without a housing Droppy uses an island shape; shelf widget contexts expose usesIslandCurvature for that layout.

A surface Droppy cannot mount refuses and says so: the call returns false or nil and writes one line into Droppy's log naming what it refused and why. Handle the refusal rather than branching on whether the host supports a surface, because that branch ages badly. Whether the user granted a capability is a different question.

The SDK's design guidelines carry every number above: Design guidelines, Shelf widgets, Live activities and Expanded surfaces. The companion guide to building a droplet covers the toolchain, the bundle and the harness.

Sources and version scope

Checked against the current product: the surface set, the descriptor requirements, the content insets, the seat arbitration and its reasons, the HUD's strip and card shapes, the suppression options and the presentation identity come from the DroppyKit SDK and its guides as they stand for Droppy 15.3.0: Design guidelines, Shelf widgets, Live activities, Expanded surfaces and Host support, which names the Droppy version each surface is mounted in.