Skip to content

ADR-0063: One settings row with a vertically centred trailing slot

  • Status: Accepted
  • Date: 2026-10-17
  • Amended: 2026-10-20 (Warm ledger — 48 dp rows, 36 dp leading tile, section label outside the card; ADR-0079)

Context

Each settings row was its own Material 3 ListItem. On a row that carries a supporting line (a task description, a rule count), the trailing chevron did not read as centred against the card's visible height — it looked top-heavy — and every section re-decided the leading/trailing arrangement for itself. There was no single place that owned "title, supporting text and trailing control share the row's vertical centre" (issue #173).

Decision

ui/components/SettingsRow.kt owns the layout: a Row with verticalAlignment = Alignment.CenterVertically wrapping a leading slot, a weighted title + optional supporting text, and a trailing slot. Because the trailing control is centred against the tallest child, a chevron, switch or checkbox sits on the row's vertical centre whether the row is one line or several. The component owns geometry only: callers pass the background, clickable/toggleable behaviour and semantics through modifier, so the toggle-accessibility contract (one named switch-role row, ADR-0046 area) is unchanged. SettingsNavigationRow builds the chevron destination row (leading icon + trailing chevron on surfaceContainerLow) on top of it, and every settings section — the index tasks, the rules list, supported currencies, tracked banks, capture toggles, appearance, security — routes through one of the two. Screens no longer hand-roll their own chevron row (ADR-0065).

Consequences

The trailing slot is centred by construction, so the top-heavy chevron cannot return. Row heights are driven by content rather than ListItem's fixed slots; rows keep a ≥48 dp target. New settings rows pick a slot (navigation vs. a trailing control) instead of copying a ListItem.

Warm ledger amendment (2026-10-20)

The row is at least 48 dp tall with a 12 dp vertical inset. A leading slot renders in a shared 36 dp rounded primaryContainer tile (8 dp corner); the title is the 14/20 identity role and the supporting line the 12/16 supporting role. The hosting section's label sits outside and above the card as an uppercase eyebrow (ADR-0068/ADR-0079); a row never renders a section label, circle or divider. SettingsNavigationRow uses the same tile with a chevron, so both forms read as one control.

Verification

SettingsRowTest measures the trailing chevron's centre against the row's centre at 100% and 200% text (including a wrapping supporting line), and checks the navigation row keeps its click target and merged title and that a trailing IconButton keeps its contentDescription. SettingsIndexTest, SecuritySectionTest, AppearanceSectionTest and SettingsNavigationTest cover the migrated call sites. SettingsRowTest also measures the 36 dp leading tile and the ≥48 dp row, and SettingsTaskScaffoldTest asserts the eyebrow label renders outside the card (SETTINGS_SECTION_LABEL_TAG).