List row
The one row anatomy: navigation, chats, palette results, files and tray pickers.
import { ListRow, ListRowEdit } from "@arcadia/ui"The one row anatomy: sidebar navigation, chats and projects, settings navigation, palette results, the files in a changes card and tray pickers. A 20×20 leading slot, the label, an optional description, meta at the end, and actions revealed on hover that take the meta's place in the same slot. Hover and highlight fills are instant. An icon in the leading slot draws its filled form while the row is active.
Examples
Palette rows
emphasis puts the label at text-1 beside a description. highlighted is the keyboard highlight a list drives from elsewhere, such as a palette's search field.
Inline rename
Double-click to rename: editing turns the row into a static container for a ListRowEdit. Enter or blur commits; Esc cancels.
rootStyle takes where the row sits and these knobs, each a token of its kind. The states read their own values (the current row is text-1 over --row-fill-active, a disabled one text-4), so a colour you set never drops a state.
| Knob | Takes | Default | Sets |
|---|---|---|---|
Ref<Control | Measure> | var(--control-md) | Minimum height. | |
Ref<Space> | var(--space-1) | Inline padding on the icon side: the leading slot's edge (its glyph's ink sits 2px further in). | |
Ref<Space> | Derived | calc(var(--row-px) + (${control["--control-xs"] | Inline padding on a text side (a row with no leading slot starts here, and every row ends here): the icon side plus the slot's inset, so text rows and icon rows put their first ink on one line (R3). | |
Ref<Space> | "0px" | 0px | Extra start padding for a nested row; the fill starts after it. | |
Ref<Space> | var(--space-1-5) | Gap between leading, label, description and meta. | |
Ref<Radius> | var(--radius-row) | Corner radius of the fill. | |
Paint | transparent | Fill at rest. | |
Paint | var(--fill-5) | Fill under the pointer (and while the row's actions have a menu open). | |
Paint | var(--fill-4) | Fill of the keyboard highlight a list drives from elsewhere. | |
Paint | var(--fill-3) | Fill of the current row. | |
Ref<Shadow> | "none" | none | Shadow at rest. | |
Ref<Shadow> | "none" | none | Shadow of the current row. | |
Paint | var(--text-2) | Label colour (the current row is text-1, a disabled one text-4). | |
Paint | var(--icon-2) | Leading glyph colour (the current row is icon-1, a disabled one icon-4). | |
Paint | var(--text-3) | Description colour. | |
Paint | var(--text-3) | Meta colour. | |
Paint | var(--fill-4) | The Glide's fill on this row under the pointer. | |
Paint | var(--fill-3) | The Glide's fill on this row under the keyboard. |
Without actions the row is one control: a <button>, or your element through render, such as a link. With actions it becomes a container holding the control and the action buttons side by side, so no button nests inside another. Set aria-current yourself on the row for the current page.
Actions show while the row has keyboard focus, so Tab reaches them right after it; Shift+Tab from the row below skips them while they are hidden. Keep every action in the row's menu too, so a touch screen, where nothing hovers, can reach it.
- EnterSpace
- Activates the row
- Enter
- Commits a rename
- Esc
- Cancels a rename
ListRow
size- "sm" | "md" | "lg"Default"md"
- Heights 24 / 28 / 30 (the sidebar row).
leading- IconName | ReactNode
- 20×20 leading slot: an icon name (its filled form while the row is
active), or a node (StatusDot, PineSweep, a swap pair). reserveLeading- boolean
- Keep the leading slot's width with nothing in it, so the label lines up with its siblings.
label- ReactNode
- The label (or pass it as children). Ellipsized; a Skeleton here keeps the row height.
description- ReactNode
- Secondary text after the label on the same line, text-3 (a path, a project name).
meta- ReactNode
- End slot: a relative time, a shortcut Badge, diff stats. Hidden while actions show. Never part of the row's accessible name: a shortcut (
Kbd,KbdGroup, or a Badge or text such as"⌘N"or"⏎") becomes the row'saria-keyshortcuts; anything else is its description, read the way a screen reader reads the meta (visually hidden text kept,aria-hiddenparts dropped), so the row reads "acme-web", described "3h". Static rows without a role keep it as plain text. metaOnHover- boolean
- Quiet meta: invisible until the row is hovered or keyboard-focused (a nav row's ⌘N badge).
actions- ReactNode
- Hover-revealed end actions (IconButton size="xs" tone="muted", 5px apart). They replace
metain the same slot on hover, keyboard focus, or whileactionsOpen, with no fade and no row reflow. Never a naked destructive ×; put destructive actions in the row's menu. In a tree or listbox, where only items may sit, wrap themaria-hiddenand give the row keyboard commands for the same actions. actionsOpen- boolean
- Keep actions revealed (and the hover fill) while one of them has its menu open.
active- booleanDefaultfalse
- Current item (route, open chat): fill + text-1. Set
aria-currentyourself where it applies. highlighted- booleanDefaultfalse
- Keyboard highlight in a list driven from elsewhere (palette, tray with focus in a search box).
tone- "neutral" | "muted"Default"neutral"
muted: text-3 rows ("More (4)", "No chats yet").neutral: text-2.emphasis- boolean
- Label at text-1 instead of text-2 (tray and palette rows with a description).
editing- booleanDefaultfalse
- Inline rename: the row stops being a button so a
ListRowEditcan sit in the label slot. interactive- booleanDefaulttrue
- False renders a static row (a
div, no hover fill): read-only lists, placeholders. rootStyle- ListRowStyle
- Typed StyleX override: where the row sits and its
--row-*knobs. className- string
- A product's hook class for its own rules. Being retired: new code sets knobs with
rootStyle; the call sites left are counted by ui.lint (kit-classname) and only go down. disabled- booleanDefaultfalse
- Text-4, no hover fill, not clickable.
render- ReactElement | (props, state) => ReactElement
- Replace the rendered element, e.g.
render={<a href="/settings" />}.
Rest props go to <button> props and render.
onCommit- (value: string) => void
- Enter or blur: the trimmed-or-not value is the caller's call.
onCancel- () => void
- Escape: revert.
selectOnMount- booleanDefaulttrue
- Focus and select the whole value on mount.
Rest props go to <input> props.