Lists
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 (two
line) or 2 lines (threeline); ellipsison 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-selectedon selected - Trailing icon buttons:
aria-labeldescribing action - Trailing switches:
role="switch"+aria-checked - Row that navigates: native link semantics (
<a href>wrapping rowcontent)
- 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— multiselect / singleselect 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