StackedList

Molecule
Vertical lists with avatar, two-line items, and trailing metadata. Used for contacts, activity feeds, and data previews.

Playground

Every control sets a real prop of StackedListProps. The JSX below updates as you go.
Members
  • RC
    Rachel Chen
    rachel.chen@example.com
    admin
  • AL
    Ada Lovelace
    ada@example.com
    editor
  • KT
    Kevin Turner
    kevin@example.com
    viewer
Props
Variant
pick one
flush
boolean
title
string
JSX · StackedListProps
<StackedList title="Members" />

Variants

6 examples from src/molecules/stacked-lists/stacked-lists.md, each rendered live from its own JSX.
  • KR
    Kratos
    Identity
    Healthy
  • HY
    Hydra
    OAuth2
    Degraded
  • PO
    Postgres
    Database
    Down
Status tones
<StackedList
items={[
{ name: "Kratos", detail: "Identity", badge: "Healthy", success: true },
{ name: "Hydra", detail: "OAuth2", badge: "Degraded", warning: true },
{ name: "Postgres", detail: "Database", badge: "Down", error: true },
]}
/>
Clickable
<StackedList
items={[
{ name: "Rachel Chen", detail: "rachel.chen@example.com", meta: "2h ago" },
{ name: "Ada Lovelace", detail: "ada@example.com", meta: "5h ago" },
{ name: "Kevin Turner", detail: "kevin@example.com", meta: "1d ago" }
]}
clickable
/>
Team members
  • RC
    Rachel Chen
    Engineering Lead
  • AL
    Ada Lovelace
    Staff Engineer
Card
<StackedList
card
title="Team members"
items={[
{ name: "Rachel Chen", detail: "Engineering Lead" },
{ name: "Ada Lovelace", detail: "Staff Engineer" }
]}
/>
Team members
  • RC
    Rachel Chen
    Engineering Lead
  • AL
    Ada Lovelace
    Staff Engineer
Custom header action
<StackedList
card
title="Team members"
action={<Button ghost small>Invite</Button>}
items={[
{ name: "Rachel Chen", detail: "Engineering Lead" },
{ name: "Ada Lovelace", detail: "Staff Engineer" }
]}
/>
  • RC
    Rachel Chen
    rachel.chen@example.com
  • AL
    Ada Lovelace
    ada@example.com
  • KT
    Kevin Turner
    kevin@example.com
Reorderable
Each row gains a leading grip: drag it, or focus it and press Space to grab, the arrow keys to move, Space to drop, Escape to cancel. The order stays controlled: a drop only reports the move (fromIndex/toIndex plus the new afterId/beforeId neighbors), and the consumer applies it to its own array.
<Stateful initial={[
{ id: "rachel", name: "Rachel Chen", detail: "rachel.chen@example.com" },
{ id: "ada", name: "Ada Lovelace", detail: "ada@example.com" },
{ id: "kevin", name: "Kevin Turner", detail: "kevin@example.com" },
]}>
{(people, setPeople) => (
<StackedList
reorderable
items={people}
onReorder={({ fromIndex, toIndex }) => {
const next = [...people];
next.splice(toIndex, 0, ...next.splice(fromIndex, 1));
setPeople(next);
}}
/>
)}
</Stateful>
  • RC
    Rachel Chen
    rachel.chen@example.com
  • AL
    Ada Lovelace
    ada@example.com
    editor
Inline trailing control
A per-item trailing slot renders right-aligned before the badge/meta cluster, for an inline editor or affordance. In the clickable variant a row carrying the slot moves its press target to the content region, so the control never nests inside the row pressable.
<StackedList
items={[
{ name: "Rachel Chen", detail: "rachel.chen@example.com", trailing: <Chip selectable defaultSelected>Owner</Chip> },
{ name: "Ada Lovelace", detail: "ada@example.com", trailing: <Chip selectable>Owner</Chip>, meta: "editor" },
]}
/>

States

Hover, focus and press are pinned so they can be compared side by side; the rest are real props.
Members
  • RC
    Rachel Chen
    rachel.chen@example.com
    admin
  • AL
    Ada Lovelace
    ada@example.com
    editor
  • KT
    Kevin Turner
    kevin@example.com
    viewer
default
Members
  • RC
    Rachel Chen
    rachel.chen@example.com
    admin
  • AL
    Ada Lovelace
    ada@example.com
    editor
  • KT
    Kevin Turner
    kevin@example.com
    viewer
hover

Anatomy

Members
  • RC
    Rachel Chen
    rachel.chen@example.com
    admin
  • AL
    Ada Lovelace
    ada@example.com
    editor
  • KT
    Kevin Turner
    kevin@example.com
    viewer
1
List
2
Header
3
Row
4
Leading avatar
5
Trailing
Dimmed parts appear only in some states or variants.

Props

16 props on StackedListProps, grouped as they are declared in source. Required props are marked *.
Prop
Type
Default
Description
Props
8 props
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
false
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.
Variant
pick one
2 props
clickable
boolean
false
card
boolean
false
Divider modifier
6 props
flush
boolean
false
virtualized
boolean
false
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
false
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.

Accessibility and keyboard

Keyboard
Tab
Moves between links
Enter
Follows the link
Semantics
Roles
button
ARIA on web
aria-hidden
Below the platform minimum, the touch area grows to 44 pt on iOS and 48 dp on Android without moving the layout.
From the docs
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 l

Platform skins

The playground's props on each platform: same tree, native shapes, type and touch sizes.
  • RC
    Rachel Chen
    rachel.chen@example.com
  • AL
    Ada Lovelace
    ada@example.com
    editor
Web
control 10 · field 10 · card 16 · Instrument Sans
  • RC
    Rachel Chen
    rachel.chen@example.com
  • AL
    Ada Lovelace
    ada@example.com
    editor
iOS
capsule controls · field 8 · card 12 · SF Pro · 44 pt
  • RC
    Rachel Chen
    rachel.chen@example.com
  • AL
    Ada Lovelace
    ada@example.com
    editor
Android
Material 3 stadium · field 4 · card 12 · Roboto · 48 dp

Usage

<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" }
]}
/>
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.

Tokens

Read from the playground render. Values follow the scheme and the ion hue.
--fd
--ft
--ion-soft
--ion-tx
--ln
--ok
--ok-soft
--tx3

Related components