Components / Lists

Row macrolite

THE list row: a lead glyph or slot, a mono identifier, a title with a subtitle, trailing meta or a readable value, a slot and a chevron.

Built from
BoxIconText
Replaces
CardRowListRowNavRowPickerRowPropertyRow
Children
none
Loading the renderer…
Row rendered by the React renderer on Web
Web React
Row rendered by the SwiftUI renderer on iOS
iOS SwiftUI
Row rendered by the Compose renderer on Android
Android Compose
Row rendered by the gpui renderer on Desktop
Desktop gpui

View exponential-ui-row, painted by each native renderer.

chevron · none
chevron · right
chevron · selector
selected
density · default
density · compact
surface · flat
surface · card
PropTypeDefaultDescription
titlerequiredbindstring–The main text.
identifierbindstring–A mono identifier before the title (EXP-12).
subtitlebindstring–A muted second line.
metabindstring–Trailing muted text (a count, a time).
valuebindstring–A trailing readable value (a property, a pick).
placeholderstring–Muted trailing text while value is empty.
iconicon–The lead glyph (the leading slot replaces it).
chevronnone | right | selector"none"A trailing disclosure (right) or picker (selector) glyph.
selectedbooleanfalseDraws it as the active row.
densitydefault | compact"default"Row height class.
surfaceflat | card"flat"A flat row, or a padded card with its own fill and border.
pressablebooleanfalseMakes it a press target that fires press (an on.press implies it).
depthnumber0Nesting depth under a tree Section (0 = root); the guides are computed from the rows around it.
NameKind
on.pressevent: runs an action
leadingslot: one component by id
trailingslot: one component by id
Role
button
Semantics
button (aria-selected when selected) when pressable, a group of its texts otherwise ($a11y); the guides are decorative
KeysAction
EnterSpacepress (pressable only)
Recipe parts
Row/rootRow/guidesRow/iconRow/identifierRow/bodyRow/titleRow/subtitleRow/metaRow/valueRow/chevron
Keyed by
selecteddensitysurfacedepth:hover:pressed:focus:disabled:checked:open:selected:invalid:dragover

Themes · Write a theme

Property row

EXP-1170: THE phone properties sheet row (issue detail + create form, ×3 phones), the list row's VALUE variant (was PropertyRow; the catalog `Row` with a `value`): the label left and muted, the value right and readable with its glyph riding beside it as one trailing unit, 44px, the WHOLE row the target (a Button, so it can be a picker's trigger), no chevron.

More

Rows stack inside a `GlassGroup`. A set-valued property (Labels) shows the picks joined by ", " in the team's order and opens the shared picker sheet, never a cloud of toggle chips; nothing picked reads "None".

Webleftover
PropertyValueRowapps/web/src/components/issue-editor/mobile-properties.tsx

the value variant is ListRow asChild over a Button; ListRow itself has no value prop (the catalog Row's `value` is the contract)

Desktopn/a

Desktop shows the properties inline as header chips; no sheet rows.

Glass row

The GAPPED card item (the catalog `Row surface: card`): radius 10, row fill, its own hairline border, padding 12.

More

EXP-818/1076 keep it for REAL CARDS only (a transcript's tool output, a diff) — never a settings list: the settings ladder is a `GlassSectionHeader` band over `SETTINGS_LIST_CLASS` + the flat list row below.

List row

EXP-818: the flat list item every list wears — no stroke, no fill, radius 10, padding 12, NO gap between rows under a group band; hover takes the row fill, the selected row the active fill.

More

Rows read as a table, not as cards. EXP-962 gave it a second density: `compact` is the 28px one-line row the narrow column runs at (the sidebar's pinned and draft arms, the compact inbox) — the same 14px type, 8px of side padding, 8px to the glyph — and `SidebarMenuButton density="compact"` is its exact twin, so a nav entry and a list row sitting in the same 17rem slot are the same height.

Desktopok
surface::flat_row / flat_row_compactapps/desktop/crates/ui/src/surface.rs

EXP-963: flat_row_compact is the 28px density the rail's entries run at

List item

EXP-1248 (fixture `list-item.json`, ×4): THE anatomy every entity row derives from, on a flat list row: [tree guides][lead glyph in a 14px box at x = 12 + 14·depth][mono identifier][title][caption, two-line rows only][trailing meta], which is the catalog `Row` (`depth`, `icon`, `identifier`, `title`, `subtitle`, `meta`).

More

The lead box sits at the same x on every row of a depth, so glyphs and titles align and a child's elbow ends at its own lead. No fold chevron, no trailing chevron, no buttons: a row opens, its menu is a context menu / long-press. Two densities: `compact` (one line, 28-32px: the sidebar's Running, pinned rows) and `list` (36px one-liners, 52px with a caption). Derivations differ only in lead and meta: SessionRow = the run mark + the host device's glyph; an issue row = the status glyph + the assignee; PrRow = the PR ring (a stack swaps the guides for its 1px rail).

Webok
SessionRow / PrRow over ListRowpackages/ui/src/session-row.tsx

PrRow + StackRail in pr-row.tsx; the issue row is apps/web components/issue-list.tsx

iOSok
SessionRow / PrRow over .flatRow()apps/ios/ExpUI/Sources/SessionRow.swift

PrRow.swift beside it; caption ExpCore Domain/SessionRow.swift

Androidok
SessionRow / PrRow over Modifier.flatRow()apps/android/app/src/main/java/com/exponential/app/ui/components/SessionRow.kt

PrRow.kt beside it; caption domain/SessionRow.kt

Desktopok
run_rows::run_rowapps/desktop/crates/ui/src/run_rows.rs

over surface::flat_row; pr_rows.rs = PrRow, issue_list.rs = the issue row

Tree guides

EXP-965: the connector every NESTED list draws instead of bare indentation.

More

A row used to hang under its parent by left padding alone, so three levels of runs read as three arbitrary margins. The indent stays 14px per level; on top of it a row at depth d draws, in its PARENT's 14px gutter, a 1px vertical from its top edge to its vertical centre, a rounded elbow (radius 5) and a stub out to the gutter's right edge — and the vertical carries on to the bottom edge when a sibling follows (a tee). Every ancestor level whose subtree continues below draws a straight full-height line, so a deep child stays attached to every level above it. One hairline weight throughout (the strong glass stroke); a parent draws nothing of its own. The RULE is pure and shared ×4 — it reads nothing but the visible rows' depths — so only the painting is per-platform. In the catalog the guides are a PART of `Row` (its `depth`, inside a `Section tree`), filled by the core's post-expansion pass and never authored on their own.

Webok
TreeGuides / treeGuidespackages/ui/src/tree-guides-view.tsx

the pure rule is tree-guides.ts; drawn as one absolutely positioned SVG inside the row

iOSok
TreeGuides / TreeGuidesOverlayapps/ios/ExpCore/Sources/Domain/TreeGuides.swift

the overlay is ExpUI/Sources/TreeGuidesOverlay.swift

Androidok
TreeGuides / TreeGuidesOverlayapps/android/app/src/main/java/com/exponential/app/domain/TreeGuides.kt

the overlay is ui/components/TreeGuidesOverlay.kt

Desktopok
Guides / guides_forapps/desktop/crates/domain/src/tree_guides.rs

domain::tree_guides; the painter is crates/ui/src/tree_guides.rs

Row shell

The rhythm every grouped row inherits: padding 12/16, gap 12, 14px text.

More

The shell never draws a stroke — the group's hairlines do.

Picker row

Label left, value right-aligned at 70% foreground, a 14px chevron at 50%.

More

The whole row is the target, never just the value.

Webok
Picker triggerVariant="row"packages/ui/src/picker/picker.tsx

EXP-958: the row IS the picker — its own Select and sheet are gone

Desktopok
controls::rows::glass_picker_rowapps/desktop/crates/exponential-ui-gpui/src/controls/rows.rs

Picker surface

The searchable SURFACE every Picker opens (Picker = the only public picker; its Combobox engine is internal since EXP-1249): a popover on a pointer, the bottom sheet on a phone.

More

Selection speaks ONE language: SINGLE marks the picked row with a trailing ui-check, MULTI by the row's own highlight wash (data-picked), and a bulk edit over rows that disagree marks the row mixed; never a checkbox, never a circle pair. `value` is the identity and `keywords` the search text, so two boards may share a name; `noneLabel` renders a row that reports null. Single closes on pick, multi stays open. PickerItem is the row shape; the trigger comes in four `triggerVariant`s, pill | field | row | inline (row = the form ladder's picker row, the catalog `Row chevron: selector`). PickerMenuRows draws the same rows inside a Menu.

Webok
Picker / PickerListpackages/ui/src/picker/picker.tsx

PickerItem is the row shape; PickerList the body without the popover; PickerMenuRows the rows inside a menu

iOSok
GlassPickerapps/ios/ExpUI/Sources/Picker/Picker.swift

EXP-1249 retired GlassPickerSheet: every pick outside the typed subjects goes through GlassPicker

Androidleftover
GlassSheetRowapps/android/app/src/main/java/com/exponential/app/ui/components/GlassSheet.kt

EXP-1021 built the generic picker (Picker, its own entry); this row is what the sheets outside the ten assemble

Desktopok
picker::Pickerapps/desktop/crates/ui/src/picker/mod.rs

EXP-1021 retired searchable_picker; the primitive owns its query + cursor, so a host holds no picker entities