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.
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.

Choosing the surface
| Surface | Reach for it when | Controls | Lifetime |
|---|---|---|---|
| Shelf widget | There is something to look at, change or configure | Yes | While the shelf is open |
| Live activity | A status a glance should answer | In the row | While the work runs |
| Mini HUD | A short confirmation or a transient progress step | In a card | Seconds |
| Expanded surface | The task needs the whole shelf: a picker, a form, a detail view | Yes | Until 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.