Skip to content

ListRow

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/list-row.json
See it liveReleases as facts in a panel, versions as choices with one open, and a long title cut to its line.→

Every screen has a list, and every product drew its own row. kilna alone had six list-and-detail layouts with five row recipes, and the open row was tinted four ways. A row is three slots and a rule about truncation — small enough to get wrong every time, which is why it is here.

A row is a fact or a choice, and they are two components.

  • ListRow is a fact — “Clip, 02.09” in a list of releases. It is not pressable and has no hover; rows in a run are divided by a hairline the last one does not draw, so a list inside a panel ends at the panel’s edge.
  • RowButton is a choice — a version in the list beside the editor, an item in a queue. It is a real <button> (or your link, through render), it answers the pointer, and the open one is tinted on accent-soft and named as current.
<ul>
{releases.map((release) => (
<ListRow key={release.id} render={<li />} start={<StatusDot status={release.status} />} end={<time>{release.date}</time>}>
{release.title}
</ListRow>
))}
</ul>
{versions.map((version) => (
<RowButton
key={version.id}
selected={version.id === open}
description={version.author}
end={<RelativeTime value={version.updatedAt} />}
onClick={() => setOpen(version.id)}
>
{version.title}
</RowButton>
))}

Three slots, the same in both. start identifies the row at a glance — an icon, a status dot, a cover. The row’s words, with an optional description under them. end is what is measured about it — a date, a count, a chip, set in tabular figures so a column of them lines up.

The words truncate; the slots keep their size. A list whose rows grow to two lines in one language and not the other stops being a list you can scan.

The open row is announced, not only tinted. RowButton sets aria-current="true", which is what a list beside an editor is: where you are, not a value you are submitting. A product that puts rows in a listbox passes aria-selected and aria-current={undefined}, and what it passes wins.

A ListRow’s height follows density. It stands on py-row, the same rhythm a Table row does, so data-density="compact" tightens a list and a table together.

Props

Read from the types in ui/list-row.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.

ListRow

A row that states a fact. Rows in a run are divided by a hairline that the last one does not draw, so a list inside a panel ends at the panel's edge rather than on a line of its own.

PropTypeDefault
startReactNodeWhat identifies the row at a glance: an icon, a status dot, a cover.
descriptionReactNodeA second line under the words, quieter and truncated the same way.
endReactNodeWhat is measured about the row: a date, a count, a chip.
childrenrequiredReactNodeThe row's words.
renderRenderProp<Record<string, unknown>>Something other than a div - an <li> in a <ul>, most often: render={<li />}.

And every prop of React's HTMLAttributes<HTMLElement> except children, passed on.

RowButton

A row that is a choice.

PropTypeDefault
startReactNodeWhat identifies the row at a glance: an icon, a status dot, a cover.
descriptionReactNodeA second line under the words, quieter and truncated the same way.
endReactNodeWhat is measured about the row: a date, a count, a chip.
childrenrequiredReactNodeThe row's words.
selectedbooleanfalseThe open one - tinted, and named as current.
renderRenderProp<Record<string, unknown>>The element to draw the row as - a router's link, most often: render={<NavLink to={…} />}. Without it the row is a <button>.
disabledbooleanDisables the row. Applied only when the row is the default <button>: a rendered element such as a link has no disabled of its own.

And every prop of React's HTMLAttributes<HTMLElement> except children, passed on.

Accessibility

The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 19 rules applied and passed, and none failed. The accessibility report has every item.

History

Each release that changed ui/list-row.tsx - the file a product copies - newest first.

  1. v0.41.0
    • docs read every props table from the component's types ee3b159
  2. v0.36.0
    • fix set the notes in dim, which clears AA e9e9a0e
  3. v0.33.0
    • feat rows that state a fact and rows that are a choice 10d680d