data · Normal DOM + Twenty Remote DOM

ListRow

Slot-based row — leading marker · title · subtitle · trailing · actions · built-in dots menu. The atom for any list of records.

Replaces the inline `ProductPickerRow` (DealItems) and `DocumentRow` (DocumentHub) helpers. Two appearances — `flush` (no border, divider on top, sits inside a bordered Box scroll container) and `card` (full border + radius, standalone clickable row). Hover / selected / disabled / highlighted states are driven via JS because Twenty Remote DOM strips `:hover` from inline styles. The dots-button + DropdownMenu pattern is baked in via `menuItems` so consumers cannot reproduce the double-click bug (row + menu firing at once).

Install

Pull from the workspace packages (already available if you ran pnpm add):

ts
import { ListRow } from '@8maverik8/twenty-design';

Examples

Card — DocumentHub document row, with built-in dots menu

The deal-items archetype for DocumentHub. The IconHalo identifies the source, Status shows lifecycle, trailing shows total. Clicking the row → opens a preview. Clicking the dots → opens the menu (and does NOT also fire the row click). The two paths are deliberately separated inside the recipe.

INV-2025-0042Paid
Initial subscription invoice · QuickBooks · 14 Apr 2025
€7,488
Demo logReady — click the row or the dots.
tsx
<ListRow
appearance="card"
leading={<IconHalo tone="green" icon={<IconReceipt size={18} />} />}
title="INV-2025-0042"
titleAdornments={<Status color="green" size="sm" variant="soft">Paid</Status>}
subtitle="Initial subscription invoice · QuickBooks · 14 Apr 2025"
trailing={<Text size="md" weight="medium">€7,488</Text>}
onClick={() => openPreview(doc.id)}
menuItems={[
{ label: 'Open in QuickBooks', icon: <IconExternalLink size={14} />, onSelect: openExternal },
{ label: 'View CRM data', icon: <IconEye size={14} />, onSelect: openPreview },
{ separator: true },
{ label: 'Regenerate', icon: <IconRefresh size={14} />, onSelect: regenerate },
{ label: 'Mark voided', icon: <IconBan size={14} />, onSelect: markVoided, destructive: true },
]}
/>

Flush — picker rows with states

How DealItems renders catalog products inside a bordered scroll container. Shows the three states side by side: default, disabled (already in deal), selected (newly picked).

Creomate · Bundle (WebAPI + Webhook) · 32 SCCreomate
€91.50/yr
Notion SyncNotionIn deal
€19.00/yr
OAPPS 3CX for Zendesk · Professional3CX
€99.00/yr
tsx
<Box border="light" radius="md" padding="0">
<ListRow appearance="flush" isFirst … /> {/* default */}
<ListRow appearance="flush" … disabled /> {/* in deal */}
<ListRow appearance="flush" … selected onClick /> {/* just picked */}
</Box>

Selectable — live multi-select demo

Click rows to add/remove. The checkmark column is the `leading` slot, swapped by the consumer based on selection state.

Creomate · Bundle · 32 SCCreomate
€91.50/yr
Creomate · Bundle · 48 SCCreomate
€130.00/yr
Notion SyncNotion
€19.00/yr
Zendesk BridgeZendesk
€45.00/yr
tsx
// see _examples/list-row.tsx

Variants

appearance
flushdefaultNo border. 1px top divider between adjacent rows. Use inside a bordered scroll container.
cardFull border + radius. Standalone clickable row (e.g. DocumentRow inside Stack gap="2").
state
selectedPicked / in-cart row — accent-tertiary background.
highlightedSofter "current" state — also accent-tertiary, separate prop so the consumer can express both at once if needed.
disabledNot selectable (e.g. already in deal). Opacity 0.65, cursor not-allowed.
titleSize
smdefaultPicker / catalog row title. Lighter and more readable in dense lists.
mdIdentifier rows — document number, order code, invoice ID.
titleWeight
regularQuiet entries — drafts, archived items.
mediumdefaultDefault — readable without shouting.
semiBoldProminent identifier rows.

Anatomy

leadingOptional leading column — checkmark, IconHalo, Avatar. 24–36px wide.
titleMain line. Default sm + medium weight (matches picker rows). For prominent identifier rows (document number, order code) pass titleSize="md" titleWeight="semiBold".
titleAdornmentsInline badges next to the title — Tags, Status, version pills.
subtitleSecondary line (size xs, tertiary).
trailingRight-side meta — price, total, date, /yr suffix.
actionsCustom IconButton / button cluster (rendered BEFORE the menu).
menuItemsBuilt-in dots-button + DropdownMenu. Click on this trigger does NOT fire the row onClick — propagation is stopped inside the recipe.

Guidelines

Prefer `menuItems` over `actions` for overflow actions.The recipe owns the double-click bug fix. Building your own dots+menu means re-implementing the stopPropagation and the consistent right-edge layout.
Use appearance="flush" inside a bordered Box with maxHeight + overflowY:auto.The picker pattern is meant to read as one cohesive scrollable list — individual borders would be visual noise.
Use appearance="card" when rows stack in a Stack with gap.Cards need their own border so the gap looks intentional, not like a missing divider.
Nest your own clickable elements inside the `actions` slot without stopPropagation.The recipe stops bubbling on the slot wrapper, so simple children are safe. But a nested clickable that itself calls preventDefault on something else can still cause surprises — keep `actions` reserved for primitives like IconButton.