---
title: "ShelfWidgetScaffold, DroppyKit"
description: "A widget&#x27;s whole layout (DroppyKit 1.23.0): the header row, the content laid out at the room that is left, and an optional last row of controls on the card&#x27;s bottom line."
canonical: "https://getdroppy.app/docs/droppykit/api/shelf-widget-scaffold"
last-updated: "2026-09-09"
---

# ShelfWidgetScaffold

A widget's whole layout (DroppyKit 1.23.0): the header row, the content laid out at the room that is left, and an optional last row of controls on the card's bottom line.

`@MainActor struct ShelfWidgetScaffold<Trailing, Content, Footer> where Trailing : View, Content : View, Footer : View`

It is the layout every Droppy widget shares, with Droppy's numbers:

- the header on the top edge, wrapped beside the camera where the host offers it ([`ShelfWidgetHeader`](https://getdroppy.app/docs/droppykit/api/shelf-widget-header));
- [`sectionSpacing`](https://getdroppy.app/docs/droppykit/api/droppy-widget-metrics#section-spacing) down to the content, which is laid out with zero minimums, so a varying list never grows the card;
- alone, the content hangs from the header; in a row the room a taller neighbour gives opens above it, so its last row ends on the row's bottom line beside the built-ins;
- the footer, a [`controlRowHeight`](https://getdroppy.app/docs/droppykit/api/droppy-widget-metrics#control-row-height) row on the bottom line, its controls centred, so control rows on both sides of a divider share their centre line.

```
ShelfWidgetScaffold("Disks", systemImage: "internaldrive") {
    Button("Eject all", systemImage: "eject") { ejectAll() }
} content: {
    ShelfWidgetRows(disks) { disk in
        ShelfWidgetValueRow(disk.name, value: disk.free)
    }
}
```

Available from DroppyKit 1.23.0.

## Initializers

### init(_:systemImage:dso:content:)

`@MainActor init(_ title: String, systemImage: String? = nil, dso: UnsafeRawPointer = #dsohandle, @ViewBuilder content: () -> Content)`

A header with no controls, and the content.

### init(_:systemImage:dso:content:footer:)

`@MainActor init(_ title: String, systemImage: String? = nil, dso: UnsafeRawPointer = #dsohandle, @ViewBuilder content: () -> Content, @ViewBuilder footer: () -> Footer)`

A header with no controls, the content, and a last row of controls.

### init(_:systemImage:dso:trailing:content:)

`@MainActor init(_ title: String, systemImage: String? = nil, dso: UnsafeRawPointer = #dsohandle, @ViewBuilder trailing: () -> Trailing, @ViewBuilder content: () -> Content)`

A header with controls, and the content.

### init(_:systemImage:dso:trailing:content:footer:)

`@MainActor init(_ title: String, systemImage: String? = nil, dso: UnsafeRawPointer = #dsohandle, @ViewBuilder trailing: () -> Trailing, @ViewBuilder content: () -> Content, @ViewBuilder footer: () -> Footer)`

The full scaffold.

- Parameters:
- title: The header's title.
- systemImage: The header's symbol.
- trailing: The header's controls.
- content: The widget's content.
- footer: A last row of controls, on the card's bottom line.
- dso: Filled in by the compiler; never pass it.

## Properties

### body

`@MainActor var body: some View { get }`
