Skip to content

ListRow

One row of a mobile list (48px or taller). Swipe actions are always also in a visible More actions button.

  • Pro, mobile
  • Version 2.0.0-rc.1
  • APG pattern: List with links or buttons; swipe actions mirrored in a menu button

Examples

Still previews of the real component, rendered from its stories. Pro code isn’t public, so nothing in them responds. Switch the theme and density for every preview at once.

Default

Show the code

Default, code

<List aria-label="Inbox">
  <ListRow title="Design review" description="Tomorrow, 10:00" href="#review" actions={[{ id: 'archive', label: 'Archive', icon: Archive }, { id: 'delete', label: 'Delete', icon: Trash2, tone: 'destructive' }]} />
  <ListRow title="Sprint plan" description="Edited 2 hours ago" href="#plan" actions={[{ id: 'edit', label: 'Edit', icon: Pencil }]} />
  <ListRow title="Read me" description="Shared with the team" href="#readme" />
</List>

Trailing and selected

Show the code

Trailing and selected, code

<div className="flex flex-col gap-stack-lg">
  <List aria-label="Network">
    <ListRow title="Wi-Fi" trailing="Home" href="#wifi" />
    <ListRow title="Bluetooth" trailing="On" href="#bluetooth" />
  </List>
  <List aria-label="Language">
    <ListRow title="English (UK)" onPress={() => {}} isSelected />
    <ListRow title="Français" onPress={() => {}} />
    <ListRow title="Deutsch" onPress={() => {}} />
  </List>
</div>

Amounts

Show the code

Amounts, code

<List aria-label="Transactions">
  <ListRow title="Coffee House" description="Card payment" amount="-£4.20" amountDetail="Pending" href="#t1" />
  <ListRow title="Salary" description="Bank transfer" amount="+£2,450.00" amountDetail="28 Oct" href="#t2" />
</List>

Desktop

  • 2.4 MB
  • 18 MB
  • 4 KB
Show the code

Desktop, code

<List aria-label="Files">
  <ListRow layout="desktop" title="Q3 report.pdf" meta="Edited 2 hours ago" trailing="2.4 MB" onPress={() => {}} isSelected />
  <ListRow layout="desktop" title="Brand guidelines.fig" meta="Edited yesterday" trailing="18 MB" onPress={() => {}} />
  <ListRow layout="desktop" title="Notes.md" meta="Edited 3 days ago" trailing="4 KB" onPress={() => {}} />
</List>

Tokens

The 18 design tokens ListRow reads, with the value each has in the theme and density chosen above. Change a token and the component follows, in Figma and in code.

Show all 18 tokens
Tokens ListRow reads
TokenValue nowCSS variable
color.action.danger…--sd-color-action-danger
color.action.neutral…--sd-color-action-neutral
color.bg.canvas…--sd-color-bg-canvas
color.bg.subtle…--sd-color-bg-subtle
color.border.subtle…--sd-color-border-subtle
color.icon.default…--sd-color-icon-default
color.text.on-action…--sd-color-text-on-action
list.check…--sd-list-check
list.fg…--sd-list-fg
list.fg-secondary…--sd-list-fg-secondary
list.row-pressed…--sd-list-row-pressed
list.row-selected…--sd-list-row-selected
list.selected-edge…--sd-list-selected-edge
spacing.gap.md…--sd-spacing-gap-md
spacing.inset.md…--sd-spacing-inset-md
spacing.inset.sm…--sd-spacing-inset-sm
spacing.inset.xs…--sd-spacing-inset-xs
target.min…--sd-target-min

Install

Pro components come as source from the SuperDuper registry, with a licence key from Solo, Team or Studio. The registry and checkout aren’t open yet.

Add it from the registry (Pro licence key)

npx shadcn@latest add @superduperui/list-row

Then import it

import { List, ListRow, SwipeAction } from "@/components/ui/list-row"

Props

ListRow props
PropTypeWhat it does
titlerequiredstring
descriptionstring
leadingReactNodeLeading visual (an Avatar, an icon in a tile). Decorative unless it carries its own name.
trailingReactNodeTrailing text or a control that is not the row's action (a value, a Switch). On a link row it sits inside the link, before the chevron, so it is read with the title ("Wi-Fi, Home"): keep it text there, never a control.
amountstringA money value at the end of the row (tabular), with an optional second line under it (a status, a date).
amountDetailstring
metastringDesktop rows: a short secondary value before the trailing one ("Edited 2 hours ago").
isSelectedbooleanDefault falseThe current choice in a single-choice list: a tick and the selected tint (desktop: an edge bar), and aria-current.
layout'mobile' | 'desktop'Default 'mobile'mobile (default): the 48px touch row. desktop: the dense row (44px target, smaller insets, a selected edge bar).
hrefstringThe row's own action: a link...
onPress() => void...or a press handler.
actionsListRowAction[]Actions revealed by swiping the row left. They are always also in a visible "More actions" button at the end of the row, so swiping is never the only way (2.5.7).
onAction(id: string) => void
classNamestring

When to use it

Use it for

  • Mobile lists: inbox, settings, contacts

Use something else for

  • Tabular data with columns: use Table or DataTable
  • Desktop navigation: use Sidebar

Accessibility

Screen readers announce "Design review, Tomorrow, 10:00, link" then "More actions for Design review, button".

WCAG 2.2 AAA criteria the component meets in Comfortable density: 1.4.6, 1.4.9, 2.1.3, 2.3.2, 2.4.13, 2.5.5, 2.5.6.

Compact density meets AA (2.5.8, 24px targets), not AAA.

What your product still has to do

  • Never put an action only behind a swipe

This release’s test results

Test results for ListRow in this release
axe-core (WCAG A, AA, AAA and best practice)120 runs across every story, theme, density and width: 0 violations
Rendered text contrast240 text runs: 0 below 7:1, lowest 9.8:1
Target size54 targets: 0 under 44px in Comfortable
Focus appearance (2.4.13)90 focus stops in 3 browser engines: all pass
Keyboard scriptChromium 5 of 5, Firefox 5 of 5, WebKit 5 of 5

Manual passes with VoiceOver and NVDA haven’t been done yet.

Keyboard

Each step is scripted and run in Chromium, Firefox and WebKit.

ListRow keyboard steps
KeyWhat happens
TabTab reaches the first row
TabTab reaches its More actions button (the swipe actions, without swiping)
EnterEnter opens the actions
EscapeEsc closes them and returns focus
TabTab reaches the next row

In Figma

The Figma component is ListRow, with Kind (Static, Link, Button); State (Default, Hover, Focus, Pressed, Swiped); Layout (Mobile, Desktop). The Figma library isn’t published yet.

Figma to code: Kind (Static, Link = href, Button = onPress); State (Default, Hover, Focus, Pressed, Swiped; preview only); Title → title; Description → description when Show description; Leading → leading; Trailing → trailing; Show actions → actions; Layout → layout (Mobile = "mobile", Desktop = "desktop"); Selected → isSelected; Amount → amount when Show amount; Amount detail → amountDetail when Show amount; Meta → meta when Show meta.