StackedList

Vertical lists with avatar, two-line items, and trailing metadata. Used for contacts, activity feeds, and data previews.
Rows can carry a per-item trailing slot for an inline control, and reorderable adds a leading drag grip per row (keyboard- and screen-reader-operable) that reports each drop through onReorder while the order stays controlled by your items array.
The rows are a list, as in Tailwind UI, and each row is one of its items: a screen reader announces the list with its number of rows and moves through it row by row. A clickable row's button sits inside its item. The header title names the list; a list without a title takes its name from label.
A virtualized list with a bounded height scrolls its rows in a list of its own. On the web, while those rows overflow, that list is a keyboard tab stop (after the header's action, and before the rows' own buttons when they have any), the keyboard can scroll it, and while it has keyboard focus the list's card, or a plain list's frame, draws the theme's focus ring. Only the rows near its viewport are mounted, and each says where it sits in the whole list, so a screen reader still counts every row. Give a windowed list a title or a label: focused, a list with no name is named in Chromium from the text of every row it has rendered, so the kit warns in development when it has neither.
  • RC
    Rachel Chen
    rachel.chen@example.com
    admin
  • AL
    Ada Lovelace
    ada@example.com
    editor
  • KT
    Kevin Turner
    kevin@example.com
    viewer
iOS
  • RC
    Rachel Chen
    rachel.chen@example.com
    admin
  • AL
    Ada Lovelace
    ada@example.com
    editor
  • KT
    Kevin Turner
    kevin@example.com
    viewer
Android
  • RC
    Rachel Chen
    rachel.chen@example.com
    admin
  • AL
    Ada Lovelace
    ada@example.com
    editor
  • KT
    Kevin Turner
    kevin@example.com
    viewer
Web
tsx
<StackedList
items={[
{ name: "Rachel Chen", detail: "rachel.chen@example.com", meta: "admin" },
{ name: "Ada Lovelace", detail: "ada@example.com", meta: "editor" },
{ name: "Kevin Turner", detail: "kevin@example.com", meta: "viewer" }
]}
/>

Props

Prop
Description
items
StackedListItem[]
Rows to render.
title
ReactNode
Optional header title; shown above the rows, separated by a rule. It also names the list for assistive tech ("Team, list, 12 items").
label
string
The list's accessible name when it has no `title` (a `title` names the list, and wins when both are set). A screen reader announces it with the list. A `title` that is not a string names the list on the web only, and `label` then names it on iOS and Android. A `virtualized` list needs a name: on the web its scroller is a keyboard stop while the rows overflow, and Chromium names an unnamed stop from the text of every row it has rendered.
action
ReactNode
Trailing header content (e.g. an action button); only shown with a title. Takes precedence over `addAction` when both are supplied.
addAction
string
Convenience header action: renders a small outlined button with a leading plus icon and this label (e.g. "Add"). Only shown with a title and when `action` is not set. Serializable, so the playground can drive it.
rowMenu
boolean
Appends a trailing ghost overflow ("...") action-menu button to every row. Press is reported through `onPressItemMenu`.
onPressItem
(index: number, event: GestureResponderEvent) => void
Press handler for a row, by index. Only used in the `clickable` variant.
onPressItemMenu
(index: number, event: GestureResponderEvent) => void
Press handler for a row's overflow menu, by index. Used with `rowMenu`.
clickable
boolean
Variant (pick one; default is the plain two-line list).
card
boolean
Variant (pick one; default is the plain two-line list).
flush
boolean
Divider modifier: rows are ruled by default; `flush` removes the hairlines.
virtualized
boolean
Render the rows through a windowed `FlatList` instead of mounting every row up front, for large lists. Give the list a bounded height (via `style`, e.g. `{ maxHeight: 400 }`) so it can scroll; without one it warns and renders eagerly anyway. Default (omitted) mounts all rows, unchanged. Ignored (with a dev warning) when `reorderable` is set: windowing unmounts off-screen rows, which would leave them undraggable and unmeasurable mid-drag. On the web, while the rows overflow, the windowed list is a keyboard tab stop the keyboard can scroll, and the list's frame draws the focus ring while it has keyboard focus.
reorderable
boolean
Make the rows reorderable: each row gains a leading drag grip (keyboard- and screen-reader-operable: Space grabs, the arrow keys move, Space drops, Escape cancels) and the list becomes one drop zone. The order stays CONTROLLED by the consumer's `items` array: a drop only reports the move through `onReorder`, never reorders internally.
onReorder
(move: StackedListMove) => void
Fired on drop with the computed move (row id, from/to index, new neighbors).
testID
string
E2E hook forwarded to the root element.
style
LayoutStyle
Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds.

Do & Don’t

Two-line with avatar

Don’t
RC
Rachel Chen
rachel.chen@example.com
Equal weight on both lines flattens the hierarchy; the email competes with the name.
Do
  • RC
    Rachel Chen
    rachel.chen@example.com
Primary line bold; secondary line smaller and muted, truncated so long values never wrap.

Clickable

Don’t
  • AL
    Ada Lovelace
    ada@example.com
    5h ago
A drilldown row with no hover state and no chevron gives no hint it is interactive.
Do
Wrap the row in a link with a hover background and a trailing chevron to signal drilldown.

Card surface group

Don’t
Team members
RC
Rachel Chen
Engineering Lead
A header with no rule blends into the rows, and dropping the per-row action removes the affordance.
Do
Team members
  • RC
    Rachel Chen
    Engineering Lead
Separate the titled header with a rule and give each row a trailing action menu.

Reorderable rows

Don’t
  • RC
    Rachel Chen
    rachel.chen@example.com
  • AL
    Ada Lovelace
    ada@example.com
Don't reorder id-less rows: with the array index as the only key, every drop re-keys the rows under assistive tech and remounts their avatars, and the reported afterId/beforeId degrade to transient indices no backend can rank by.
Do
  • RC
    Rachel Chen
    rachel.chen@example.com
  • AL
    Ada Lovelace
    ada@example.com
Give every reorderable row a stable id and apply the reported move to your own array: identity is what keeps focus, press state, and the drag announcements attached to the right row.