Forms (form / section / labeled)

The UI snippets use generated res::str accessors from an app’s Fluent catalog; see the example messages and catalog setup.

Authoring

form((
    section((
        labeled(res::str::volume(), slider(volume).range(0.0..=100.0)),
        labeled(res::str::enabled(), toggle(enabled)),
    ))
    .title(res::str::sound()),
    section((
        labeled(res::str::name(), text_field(name)),
        label(res::str::hint()).font(Font::Footnote), // sections take arbitrary pieces, not just rows
    )),
))
  • form(sections) — a vertical run (spacing 16) that provides the shared label column to every labeled row underneath it, via the scoped environment (docs/environment.md).
  • section(children).title(t) — one grouped card: an optional footnote-style header above a rounded card (radius 10, content padded 14, spacing 10). Children are arbitrary pieces; labeled rows are just the ones that participate in alignment. A section also works standalone, outside any form.
  • labeled(text, control) — one form row: the label sits right-aligned in the form-wide column, on the same text baseline as the control (docs/baseline.md; centered instead when either side has no baseline, e.g. a toggle, or on a toolkit that reports none); the control starts at the column edge + 12. A control marked .grow() (text fields, sliders, or a row(( … )) wrapper) stretches to the row’s remaining width; others hug their natural size. Outside a form, the column is just that row’s own label width. A control whose natural width does not fit beside the column — a two-button stepper in a 280-point inspector — goes UNDER the label instead, leading-aligned with the control across the whole row (the settings-list idiom), rather than clipping at the row’s edge. A stretching control shrinks rather than overflows, so give one that needs room a .min_width(pt): a slider beside a color well then stacks instead of vanishing. The label also becomes the control’s accessible name (a live text follows its source), unless the control names itself with .a11y(|a| a.label(..)), so a form needs no repeated label.

The section-card surface

The card’s background is not a hard-coded color: the container is realized with SurfaceRole::SectionCard (day-spec ContainerProps.role), and each toolkit maps it to its own theme-adaptive grouped-content material, so cards follow light/dark mode and platform theming with no app code:

ToolkitMaterial
AppKitNSColor.quaternarySystemFillColor, resolved per-draw in drawRect: (stays live across appearance changes)
UIKittertiarySystemFillColor + layer corner radius
GTKlibadwaita’s .card style class
Qttranslucent neutral fill via a scoped stylesheet + WA_StyledBackground (no grouped-card palette role exists; a 12% gray adapts to any palette)
Android?attr/colorSurfaceContainer (Material 3; falls back to colorSurfaceVariant) in a rounded GradientDrawable
XAMLCardBackgroundFillColorDefaultBrush theme resource
ArkUItranslucent neutral fill + corner radius

Layout: how alignment works

form plants a shared Rc<Cell<f64>> column width in the environment; each LabeledLayout registers its label’s unconstrained width during measure and reads back the running max in place. A row that stacks registers nothing: its label sits above its control, not in the column, so a long label on a narrow screen (a French settings row on a phone) leaves the other rows’ controls where they were instead of pushing them off the row. Within one layout pass the enclosing stacks measure every child before placing any, so by place time the max is final; alignment is consistent with no invalidation pass needed. Rows report the proposed width as their size (labels align form-wide, controls may stretch), so a growing column width never changes a row’s measured size and can’t oscillate the pass.

Vertically the row asks both children for their first text baseline (docs/baseline.md) and shifts each down to the deeper of the two, so the label meets the control’s inset text instead of the middle of its box. The row is then as tall as that shared baseline plus the deepest descent below it. When either side reports no baseline the row centers both, as before.

Verification

  • day-pieces mock e2e (form_aligns_labels_and_sections_carry_the_card_surface): two sections carry the card role + radius; label right edges align across sections; control left edges align across sections.
  • The showcase uses forms on the Controls, Canvas & shapes, Device & sensors, and the four Platform pages; the walkthrough drives every control inside them on all targets, and the three screenshot variants (light / dark / fr) verify the card material adapts per theme on every toolkit in CI.

Follow-ups

  • labeled caption/footer affordances (secondary line under the control, iOS-settings style).
  • Optional per-section label column (opt out of form-wide alignment for asymmetric sections).