Skip to content

InboxList

<InboxList> is the canonical two-pane inbox layout — selectable rows on the left, preview of the selected item on the right. Built-in j / k row navigation, Enter to open, e to archive, ⌘Enter to mark-read-and-next.

import from "@na/ui/components/InboxList" ▶ Open in Storybook packages/ui/src/components/InboxList.tsx

When to use

  • Triage queues — review requests, notifications, audit reports.
  • Anywhere “long list of things, look at one at a time” is the workflow.

Don’t use InboxList for: full lists where the user needs to scan many at once (DataTable), kanban-style stage management (BoardView), or single-pane lists.

Default

  • New review requestAcme moved a deal…
  • Build failedCI failure on dev-v2
  • Doc updateddesign-system 04-components.md
  • Comment on PRLooks good — ship it.
New review request
Acme moved a deal…
<InboxList<Item>
items={items}
selectedId={selectedId}
onSelect={(id, item) => setSelectedId(id)}
onArchive={(item) => archive(item.id)}
onOpen={(item) => router.push(`/items/${item.id}`)}
renderRow={(item, state) => (
<div className={state.unread ? 'font-medium' : 'text-muted-foreground'}>
{item.title}
</div>
)}
renderPreview={(item) => <ItemPreview item={item} />}
/>

API

Prop Type Default Description
items * T[] List rows. Must extend { id, unread? }.
selectedId string | null Controlled selected id. Defaults to the first item.
onSelect (id: string, item: T) => void Fires on click or j/k.
onArchive (item: T) => void Fires on `e` keystroke.
onOpen (item: T) => void Fires on Enter.
onMarkReadAndNext (item: T) => void Fires on ⌘Enter.
renderRow * (item, state) => ReactNode Row renderer. State has { selected, unread }.
renderPreview * (item: T) => ReactNode Preview renderer for the selected item.
header ReactNode Above the list — typically title + unread count + archive-all.
emptyState ReactNode Empty list. Defaults to a friendly "all caught up".
emptyPreview ReactNode Shown when no item is selected.
listWidth "narrow" | "wide" "narrow" narrow=360px, wide=420px.

Design guidelines

✓ Do

  • Match the keyboard contract. j/k for next/prev, Enter to open, e to archive — these are sacred.
  • Keep row renderers small — title + 1-line preview + timestamp. The preview pane is the detail.
  • Default-select the first item so the preview pane isn't empty on load.

✗ Don't

  • Use InboxList for static reference content. The keyboard overhead doesn't earn its keep.
  • Render long preview content without an internal scroll — wrap it in ScrollArea.

Accessibility

  • Rows are <button role="option"> inside role="listbox".
  • The preview is <aside> linked via aria-controls.
  • Keyboard: j/k next/prev, Enter open, e archive.
  • Timeline — Common preview content for activity-style inboxes.
  • PropertyList — Common preview content for entity-style inboxes.
  • DataTable — When the user needs to scan many at once instead.

▶ Open InboxList stories in Storybook