Designer view

List Item

A single row in a list — settings rows, contact lists, file browsers, transactions, message threads, search results. Distinct from Card (standalone object with no implicit siblings) by living inside an explicit list (`<ul>`, `<ol>`, or `<dl>`) and cooperating with sibling rows for keyboard navigation, selection semantics, and visual rhythm. Distinct from Tile (image-led, 2D grid) by being one-dimensional and text/metadata-led.

When to use

Use

For a one-dimensional list of related rows where each row carries primary identity plus optional supporting metadata — settings rows, contact lists, file browsers, message threads, search results, notification feeds. Rows cooperate via list semantics (sibling-aware keyboard navigation, selection, current-state).

Avoid

For standalone content surfaces with hierarchical composition — that is `Card`. For image-led 2D grid items — that is `Tile`. For table-like data with multiple sortable columns — that is `Table`. For floating selection from a fixed list — that is `Select`. For navigation between top-level pages with icons and sections — that is `SidebarNav`.

Versus related

  • card

    `Card` is a standalone object with no implicit siblings; `ListItem` lives inside an explicit list and cooperates with siblings. Visual treatment differs (Card is elevated/outlined surface; ListItem is dense row separated by dividers or spacing). Decision test: is each item independent (Card) or part of a sibling-aware collection (ListItem)?

  • tile

    `Tile` is image-led and arranges in a 2D grid; `ListItem` is text/metadata-led and arranges in a 1D list. ListItem suits dense metadata layouts; Tile suits image-led visual layouts.

  • table

    `Table` shows tabular data with sortable columns and column-aligned cells; `ListItem` is a free-form row with semantic slots (leading, primary, secondary, trailing). For data with multiple comparable attributes (price, date, sku, stock), use Table. For data where each row's content is heterogeneous or where visual variety matters per-row, use ListItem.

Highlight
Fig 1.1 · List Item · Designer view
Designer

Figma anatomy

Slot Figma type Hint
root instance List row component instance with leading + primary + trailing layout
leading-icon instance Icon component instance; visibility per "has icon" property
avatar instance Avatar component instance with image, initials, or icon fallback
primary text Primary text style; truncates with ellipsis when single-line variant overflows
secondary text Secondary text style; muted; visibility bound to two-line / three-line variant
trailing-icon instance Icon component instance; visibility per "has trailing icon" property
badge instance Badge component instance; visibility bound to "has badge" state
action instance Icon button or button instance at the inline-end of the row
Designer

Token usage per slot

root
spacing
  • paddingspacing.compact
  • gapspacing.compact
radius
  • cornerradius.sm
leading-icon
color
  • foregroundcolor.text.muted
avatar
radius
  • cornerradius.full
primary
color
  • foregroundcolor.text.primary
typography
  • sizetext.md
  • weightweight.medium
secondary
color
  • foregroundcolor.text.muted
typography
  • sizetext.sm
trailing-icon
color
  • foregroundcolor.text.muted
badge
spacing
  • paddingspacing.tight
radius
  • cornerradius.pill
color
  • backgroundcolor.accent.bg
  • foregroundcolor.accent.fg
typography
  • sizetext.xs
  • weightweight.semibold
action
spacing
  • paddingspacing.tight
radius
  • cornerradius.sm
color
  • ringcolor.border.focus
Both

Figma ↔ Code property map

FigmaTypeCodeNotes
VariantVariantvariantMaps standard / two-line / three-line. Drives layout — single-line is height-collapsed, two-line and three-line stack secondary text below primary.
DensityVariantdensitycomfortable / compact.
Leading TypeVariantleadingTypenone / icon / avatar. Mutually exclusive — leading-icon and avatar share the same slot position.
InteractiveBooleaninteractiveWhole row is clickable. Wraps row content in `<button>` or `<a>` activator.
SelectableBooleanselectableWhole row is selectable; visible leading checkbox appears. Drives `aria-selected` and `aria-checked`.
SwipeableBooleanswipeableMobile swipe-to-reveal-actions enabled. Pairs with action slot for parity on desktop.
Has Trailing IconBooleantrailingIcon
Has BadgeBooleanbadge
Has ActionBooleanaction
PrimaryTextprimary
SecondaryTextsecondary
AvatarInstance Swapavatar
Leading IconInstance SwapleadingIcon
Designer

Motion

TransitionDuration token
hoverStatemotion.duration.fast
swipeRevealmotion.duration.base
selectTogglemotion.duration.fast
Easing
motion.easing.standard
Reduced motion
Instant (jump cut)
Designer

Responsive behaviour

BreakpointChange
breakpoint.smAt and below, density compact becomes the canonical default. Three-line variant collapses to two-line (third line moves into a "more details" disclosure). Swipeable variant becomes the dominant action-reveal pattern (replaces hover-reveal). Action slot may collapse into the swipe-revealed actions for the primary case.
breakpoint.mdAbove this width, variants render as authored. Hover-reveal patterns engage; swipe is mobile-only unless the consumer enables it explicitly.
Both

Internationalisation

RTL · mirroring

Leading position (icon / avatar) moves from inline- start (visual left in LTR) to inline-start (visual right in RTL). Trailing position (badge / trailing-icon / action) mirrors. Primary and secondary text inherit document direction. Swipe-to-reveal direction reverses: in LTR, swipe-left exposes the inline-end actions; in RTL, swipe-right exposes the same logical-end actions. ArrowKey navigation is direction-neutral (vertical lists; ArrowDown / Up same in both directions).

Text expansion

Primary text follows single-line truncation with ellipsis (full text via `aria-label` for SR fallback). Secondary text wraps on two-line and three-line variants. Long-text languages may force two-line variant where one-line was authored — the canonical guideline is to choose the variant per the longest-expected locale, not the average. Density compact risks crowding long-text labels; density comfortable is safer for international list layouts.

Both

Variants, properties, states

Variants

Structurally different versions of the component.

standardtwo-linethree-line

Properties

The same component, parameterised.

PropertyType
interactiveboolean
selectableboolean
swipeableboolean
densitycomfortable | compact
leadingTypenone | icon | avatar

States

Browser/user-driven (interactive) vs. app-driven (data).

KindStates
interactive
hoverfocus-visibleactivedisabled
data
idleselectedcurrent
Both

State transitions

FromToTrigger
idleselectedUser toggles selection — clicking the leading checkbox (multi-select) or activating the whole row in single-select mode (`role="listbox"` parent). `aria-selected="true"` set; visual treatment changes.
selectedidleUser toggles selection again or clears via programmatic update.
idlecurrentUser navigates to the route owned by this row (for ListItem-as-nav usage). `aria-current="page"` set; visual treatment changes. Other rows in the same list lose their current state.
currentidleUser navigates away. The row loses `aria-current="page"`.
Both

Figma↔Code mismatches

  1. 01
    Figma

    ListItem drawn as a Card-like surface

    Code

    A `<li>` row inside a list, not a standalone Card surface

    Consequence

    Designers may compose ListItem mocks with Card-like treatment (rounded corners, shadow, elevated surface). Implementations following the design ship rows that look like cards but live inside lists with dense vertical rhythm — visual hierarchy is confused, the list feels noisy, and the canonical Card-vs-ListItem distinction is lost.

    Correct

    ListItem is a row in a list — visual treatment is restrained, separation between rows is via dividers or negative space, not via independent card-like surfaces. For Card-like surfaces in a layout, use Card or Tile directly; do not style ListItem as Card.

  2. 02
    Figma

    Whole-row click affordance + nested action button

    Code

    Row click + action button click both work, with action's `event.stopPropagation`

    Consequence

    Designers compose mocks with both a whole-row click target AND an inline action button. Implementations may forget the `stopPropagation` — clicking the action activates both the action AND the row's click handler; users get unexpected double-effects (delete a message AND navigate to it). Or developers may remove the action button entirely to avoid the conflict.

    Correct

    Both whole-row click and action button are valid simultaneously — the action's onClick must call `event.stopPropagation()` to prevent row activation. Document the canonical pattern; the action is a separate target in the row's tab order, distinct from the row activation.

  3. 03
    Figma

    Selected state shown only by background colour

    Code

    Selected state shown by background + leading checkbox checked + `aria-selected="true"`

    Consequence

    Designers use a single visual change for selection. Implementations following the design ship colour-only selection, fail WCAG 1.4.1, and SR users hear no selection cue. The leading checkbox is omitted; users may not realise rows are selectable.

    Correct

    Triple-layer selection: visual (background + accent strip), accessible state (`aria-selected="true"` on row, `aria-checked` on checkbox), and visible checkbox (the canonical multi-select affordance for ListItem). For single-select listbox-pattern rows, the visible checkbox may be omitted in favour of visual-state-only-plus-aria-selected.

  4. 04
    Figma

    Inline links in secondary text

    Code

    Inline interactive elements move to the action slot or break the row activation

    Consequence

    Designers compose secondary text with embedded inline links ("View profile, Edit, Delete"). Implementations following the design either ship the embedded links (which break whole-row click — clicking a link does not activate the row) or hoist them out (the design and production diverge).

    Correct

    Inline interactive elements in row content are an antipattern. Move them to the action slot as a single action button (typically more-options menu) or split the row into multiple list items. Document this as mistake `listitem-secondary-as-link`.

Designer

Common mistakes

#listitem-not-in-list

ListItem used standalone outside a list

Problem

The component is rendered without a `<ul>` / `<ol>` parent. SR users hear the content but lose the list structure — no "list of N items" announcement, no list-item position cue. Standalone usage breaks the semantic contract.

Fix

Always render ListItem inside a real list. For single-row use cases (just one row), reconsider whether the canonical pattern is ListItem or some other component (a section header, a Card, a settings row grouped as part of a fieldset).

#listitem-action-no-stop-propagation

Action button click activates parent row

Problem

The whole row is clickable; the action button is nested inside. Action click bubbles to the row's handler; users invoking the action get the row's activation as a side effect.

Fix

Action button's onClick calls `event.stopPropagation()` to prevent bubbling. Document the pattern in the action slot's a11y hint and in the canonical reference.

#listitem-no-aria-selected

Selectable rows missing `aria-selected`

Problem

Visual selected state is shown but `aria-selected` is missing. SR users hear nothing about selection state. Or `aria-selected` is on the wrong element (the checkbox, not the row).

Fix

For listbox-pattern selection, `aria-selected` is on the row (`<li>`). For multi-select with checkbox, `aria-checked` is on the checkbox AND `aria-selected` is on the row — both communicate selection redundantly. Pair with visible visual treatment.

#listitem-swipe-no-keyboard

Mobile swipe-to-delete with no keyboard equivalent

Problem

Swipeable rows expose actions on mobile via swipe gestures. Keyboard users on desktop cannot perform the swipe-revealed actions; they remain hidden.

Fix

Every swipe-revealed action also reachable via Tab and Enter. Common pattern: the action button is always in the DOM but visually hidden (or visible on hover) on desktop; swipe reveals the same buttons on mobile. Both input modalities have access.

#listitem-no-roving-tabindex

Selectable list with every row tabindex="0"

Problem

Tab cycles through every row in a selectable list (could be 100+ rows). APG listbox / grid pattern uses roving tabindex — only one row is in the tab order, ArrowKeys navigate within.

Fix

Roving tabindex per APG listbox or grid pattern. Only the currently-focused row has `tabindex="0"`; others have `tabindex="-1"`. ArrowKeys move focus within; Tab moves focus out of the list to the next document focusable.

Accessibility hints
Slot Accessibility hint
root Always wrap inside a real list (`<ul>` / `<ol>`) for the canonical list semantic. For interactive variants, the row may be wrapped in a `<button>` or `<a>` (whole- row clickable). For selectable variants, the row carries `aria-selected` and lives inside `role="listbox"` (single-select) or has a leading checkbox (multi-select). For current-page variants (in navigation lists), the row carries `aria-current`.
leading-icon Decorative when paired with a primary text label (`aria-hidden="true"`). For information-carrying icons (a verified-account checkmark, a syncing-status indicator), surface the meaning via visually-hidden text composed into the row's accessible name.
avatar For person avatars, alt text is the person's name (often redundant with the primary label — set `alt=""` then, with the avatar as decorative). For status-indicating avatars (presence dots overlaid on the avatar), surface status via visually-hidden text in the row's accessible name ("Alice (online)").
primary Plain text. The row's primary identifier for SR users. Avoid visually-hidden modifiers in the primary slot — the visible text and the accessible name should match.
secondary Plain text. SR users hear primary then secondary in DOM order when navigating into the row. Avoid embedding inline interactive elements (links, buttons) in secondary text — they break the row's whole-row-clickable contract; put them in the action slot instead.
trailing-icon Decorative when communicating affordance (chevron signalling "tap for more"); status-bearing when carrying meaning not in the primary text (a "syncing" indicator). For status, surface via visually-hidden text in the row's accessible name.
badge For numeric badges, append the count to the row's accessible name via visually-hidden text ("Inbox, 3 unread"). Decorative-only badges (a pulse for "new") use `aria-hidden="true"` with the meaning conveyed in primary text or via composed accessible name.
action Real `<button>` with accessible name. For whole-row-clickable rows, the action button's click must `event.stopPropagation()` to prevent activating the parent row. The action button is reachable via Tab; for selectable list rows, ArrowKeys do NOT navigate to the action — only Tab does.