Lists

mandatory

Vertical stack of related items, each row showing a label and optional leading / trailing elements. Material parity (`/components/lists`). Covers single-line, two-line, three-line rows; leading elements (icon, avatar, image, checkbox); trailing elements (icon, switch, metadata); and selection / nav behavior.

Spec — Lists

Facet *isual*of Koder Design. Material parity: https://m3.material.io/components/lists.

3 row densities

Density Height Use
*ingle-line* 56 px Compact list; just labels
*wo-line* 72 px Label + supporting text
*hree-line* 88 px Label + 2 lines supporting text

Pick by content. Don't mix densities within a single list — looks ragged.

Anatomy (two-line row with leading + trailing)

┌────────────────────────────────────────────────────┐
│  👤   Jamie Garcia                   ⋮              │
│       Engineering · last seen 5m ago                │
└────────────────────────────────────────────────────┘
   ↑    ↑                              ↑
 leading  primary text                trailing
         + supporting text
  • *ow height* 72 px (two-line)
  • *adding* 16 px horizontal
  • *eading element* 24 px icon / 40 px avatar / 56 px image; 16 px

    gap to text

  • *rimary text* body-large (16/24, weight 400)
  • *upporting text* body-medium (14/20, weight 400), text-muted
  • *railing* 24 px icon button OR text metadata
  • *ontainer bg* surface (transparent / parent surface)

R1 — Leading element types

Type Size Use
*con* 24 px Settings entries, semantic action label
*vatar* 40 px People / accounts
*mage* 56 × 56 px Files, products, content thumbnails
*heckbox* 18 px Multi-select list
*adio* 18 px Single-select list
*one* 0 (text starts at 16 px) Compact text lists

Within a single list, ONE leading element type — don't mix avatar rows with icon rows in the same list.

R2 — Trailing element types

Type Use
*con button*(24 px) Overflow menu, secondary action
*witch* Toggle per row (Settings)
*etadata text* Right-aligned label (file size, count, date)
*hevron*(›) Indicates row navigates somewhere
*mpty* Row is a single-purpose row (just label + leading)

Maximum 1 trailing element per row. If multiple actions needed, collapse into overflow menu.

R3 — Row interaction

Tap target Action
Whole row Primary action (navigate, open, expand)
Leading checkbox / radio Toggle selection (whole row also toggles)
Trailing icon button Secondary action (overflow, settings)
Trailing switch Toggle setting (does NOT trigger row's primary action)

When trailing has a switch, the row's primary action is the switch toggle — the entire row tap also flips the switch (and announces the change).

R4 — States

State Visual
Rest Base
Hover State layer 8% over row
Pressed State layer 12%
Focused 2 px focus ring on row edge OR on focused element
Selected (single-select) secondary-container tonal bg
Selected (multi-select) Checkbox checked + state layer 12% bg
Disabled 38% opacity on text + leading + trailing

R5 — Multi-line text rules

  • Primary text: 1 line max, ellipsis on overflow
  • Supporting text: 1 line (twoline) or 2 lines (threeline); ellipsis

    on overflow

  • Don't allow primary text to wrap to multiple lines — escalate

    density tier (single → two → three) before allowing wrap

  • For arbitrary-length content (chat preview, description), use a

    card instead

R6 — Subheaders inside a list

Use subheader divider (see components/dividers.kmd § R5) to group sections within a list:

   Recent
   ────────────────────────────────────
   👤  Jamie Garcia          ›
   👤  Pat Wong              ›
   👤  Riley Lee             ›

   All contacts
   ────────────────────────────────────
   👤  Alex Brown            ›
   👤  Chris Wong            ›

Subheader text is label-large, text-muted, 16 px left padding, 12 px top padding.

R7 — Dividers between rows

When to use When to skip
> 5 rows with same density ≤ 5 rows
Mixed-content rows (some have trailing, some don't) Uniform rows
Settings screen (helps scan) Card-like list (cards already separated)

Divider style is *nset*by default (aligned with text column, not the leading element column) — see dividers.kmd § R3.

R8 — Sticky list section headers

For long lists with many sections (contacts, files), subheaders can stick to the top of the viewport as the user scrolls:

  • Sticky behavior: when the section's first row scrolls off-screen,

    the subheader sticks to the viewport top until the next section arrives, then it transitions to the new section's subheader

  • Use sparingly — when the list is long enough that scroll position

    loses context

R9 — Empty state

When list is empty:

  • Show illustration / icon (48-64 px)
  • Show heading + brief description
  • Optional CTA button ("Add your first contact")
  • Vertically centered in available space

Don't show a literal empty list row — disambiguates from loading.

R10 — Loading state

Skeleton rows matching the actual row anatomy:

  • Same height per row
  • Leading element as gray rectangle / circle (matching shape)
  • Primary text as ~60% width gray bar
  • Supporting text as ~40% width gray bar
  • 6-8 skeleton rows by default

Fade-in real content as it loads.

R11 — Accessibility

  • List container: role="list" (HTML <ul> semantically)
  • Each row: role="listitem"
  • Selection list: role="listbox" + role="option" per row +

    aria-selected on selected

  • Trailing icon buttons: aria-label describing action
  • Trailing switches: role="switch" + aria-checked
  • Row that navigates: native link semantics (<a href> wrapping row

    content)

  • Keyboard:
    • Arrow Up / Down: moves focus between rows
    • Enter / Space: activates row's primary action
    • Tab: enters list → next focusable after list
    • Home / End: first / last row

R12 — Density (rows)

Density Single Two-line Three-line
Compact 48 px 64 px 80 px
Default 56 px 72 px 88 px
Comfortable 64 px 84 px 96 px

Inherits from customization.kmd.

R13 — Per-preset variation

Preset Row style Selected style
material3 Flat tonal, 0 px corners secondary-container bg
material2 Flat, no rounding Filled tonal bg
ios_cupertino Hairline separators, indented inset Filled blue bg
gnome Adwaita boxed-list rows with 12 px radius Accent tint
windows_11 Compact, hover band Accent bar on left edge
brutalist Solid borders between every row Inverted colors
terminal_classic Single-line > item name [trailing] Asterisk prefix * item

R14 — Forbidden patterns

  • ❌ Mixed density within a list (single + two-line rows interleaved)
  • ❌ Mixed leading element types (avatar + icon rows mixed)
  • ❌ More than 1 trailing element per row
  • ❌ Long-form text wrapping to many lines (use card instead)
  • ❌ Tap-target hit smaller than 48 × 48 px on a row
  • ❌ Trailing switch where tapping row navigates elsewhere

    (conflicting actions)

  • ❌ Selected row without strong-enough contrast (rely on color +

    border OR fill + check)

  • ❌ Loading state that flashes content before settling
  • interaction/selection.kmd — multiselect / singleselect patterns
  • interaction/states.kmd — row state layers
  • themes/color-roles.kmdsurface / secondary-container /

    outline-variant

  • themes/typography.kmdbody-large / body-medium
  • components/dividers.kmd — inset divider style
  • components/checkbox.kmd — multi-select leading
  • components/switch.kmd — trailing toggle pattern
  • foundations/elements.kmd — Container + Control families

Source: ../home/koder/dev/koder/meta/docs/stack/specs/components/lists.kmd