Lists
components specs/components/lists.kmd
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.
When this spec applies
Primary triggers
- Add a list
All triggers
- Show a vertical collection of items with labels
- Build a settings screen
- Display a contact / file / message list
Specification body
Spec — Lists
Facet Visual of Koder Design. Material parity: https://m3.material.io/components/lists.
3 row densities
| Density | Height | Use |
|---|---|---|
| Single-line | 56 px | Compact list; just labels |
| Two-line | 72 px | Label + supporting text |
| Three-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
- Row height: 72 px (two-line)
- Padding: 16 px horizontal
- Leading element: 24 px icon / 40 px avatar / 56 px image; 16 px gap to text
- Primary text:
body-large(16/24, weight 400) - Supporting text:
body-medium(14/20, weight 400),text-muted - Trailing: 24 px icon button OR text metadata
- Container bg:
surface(transparent / parent surface)
R1 — Leading element types
| Type | Size | Use |
|---|---|---|
| Icon | 24 px | Settings entries, semantic action label |
| Avatar | 40 px | People / accounts |
| Image | 56 × 56 px | Files, products, content thumbnails |
| Checkbox | 18 px | Multi-select list |
| Radio | 18 px | Single-select list |
| None | 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 |
|---|---|
| Icon button (24 px) | Overflow menu, secondary action |
| Switch | Toggle per row (Settings) |
| Metadata text | Right-aligned label (file size, count, date) |
| Chevron (›) | Indicates row navigates somewhere |
| Empty | 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 (two-line) or 2 lines (three-line); 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 inset 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-selectedon selected - Trailing icon buttons:
aria-labeldescribing 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
Cross-link
interaction/selection.kmd— multi-select / single-select patternsinteraction/states.kmd— row state layersthemes/color-roles.kmd—surface/secondary-container/outline-variantthemes/typography.kmd—body-large/body-mediumcomponents/dividers.kmd— inset divider stylecomponents/checkbox.kmd— multi-select leadingcomponents/switch.kmd— trailing toggle patternfoundations/elements.kmd— Container + Control families
References
specs/foundations/elements.kmdspecs/themes/color-roles.kmdspecs/themes/typography.kmdspecs/interaction/selection.kmdspecs/interaction/states.kmdspecs/components/dividers.kmd