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. A hover fill comes in at once and trails off over --duration-fast when the pointer leaves; the current row's pill and a keyboard highlight change in one frame, both ways. A pressable product tile takes the kit's press and sets down the same way. An icon in the leading slot sits beside the label: framed and stroked by its text, centred in the slot. The current row is marked by its fill and ink, never by the glyph.
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. A description that says why a row can't act now ("Unavailable while running") carries state: descriptionYields={false} keeps its words while the label steps down, and it steps down itself only beside the label's shortest form.
A label beside a description
Neither loses a letter.
The description gives way first: to its shorter forms (whole words, or a path's last whole folders, asideForms), then whole. Then the label ends on a whole word, or on a name boundary for a path or a single token (textForms, every label's default). The meta keeps its seat, and on hover the row's tooltip says both whole. Narrow the room to see the folder step down and go, then the name step down.
A file row
The rule above, for a file: forms hands the label its shorter forms, here nameForms, which cuts a file name at /, -, _, ., a space or a camelCase hump, so it ends on a part of the name, never inside one.
A title holds the full path, and the meta keeps its seat.
A meta that gives way
A row ranks its label over its meta.
A label that fits keeps its time; one that would be cut takes the time's room, and one longer still ends on a whole word (whole sentences, then clauses, then words, sentenceForms), its last word dimmed by the fade, never cut through. Hover or focus a long one: the time comes back and the label steps down to the words that fit beside it. The row marks its label data-fit="primary" and its meta data-fit="meta", which the layout check holds; metaYields={false} keeps the meta's seat.
Inline rename
Double-click to rename: editing turns the row into a static container for a ListRowEdit. Enter or blur commits; Esc cancels.
You are here
The active row is a flat fill-3 pill with its label in text-1: no lift and no shadow.
Hover is fill-5 and a keyboard highlight fill-4, so a hovered row never reads as selected. In a list with a Glide, the glide draws hover and highlight and the row draws only this.
People
size="person" is a row for someone you talk to: a 36px avatar in leading, the name over one sub line (the last thing said, or what they are doing now; 52px, and 40px with no line, the name centred on the avatar), aside at the end of the name's line and meta in a seat at the row's end.
A name that would be cut takes the aside's room until hover or focus; the current row, or one holding a dot or a mark (a wait's reset, which never yields), keeps it. subTone="attention" says they wait for you. Pressed, the row sets down a little.
Text in a box of your own
The label, a description and a string meta sit on their capitals, so their middles meet the row's.
Text you draw in a box of your own, such as a time in a fixed-width column, takes the trim the row hands down, --caps-trim and --caps-pad, or it sits a device pixel under the label beside it.
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.
--row-h- Ref<Control | Measure>Default var(--control-md)
- Minimum height.
--row-px- Ref<Space>Default var(--space-1)
- Inline padding on the icon side: the leading slot's edge (its glyph's ink sits 2px further in).
--row-px-text- Ref<Space> | DerivedDefault calc(var(--row-px) + (var(--control-xs) - var(--icon-md)) / 2)
- Inline padding on a text side: the icon side plus the slot's inset (R3).
--row-indent- Ref<Space> | "0px"Default 0px
- A nested row's extra start padding; the fill starts after it.
--row-gap- Ref<Space>Default var(--space-1-5)
- Gap between leading, label, description and meta.
--row-radius- Ref<Radius> | DerivedDefault var(--radius-row)
- Corner radius of the fill.
--row-fill- PaintDefault transparent
- Fill at rest.
--row-fill-hover- PaintDefault var(--fill-5)
- Fill under the pointer (and while the row's actions have a menu open).
--row-fill-highlight- PaintDefault var(--fill-4)
- Fill of the keyboard highlight a list drives from elsewhere.
--row-fill-active- PaintDefault var(--fill-3)
- Fill of the current row.
--row-shadow- Ref<Shadow> | "none"Default none
- Shadow at rest.
--row-shadow-active- Ref<Shadow> | "none"Default none
- Shadow of the current row.
--row-fg- PaintDefault var(--text-2)
- Label colour (the current row is text-1, a disabled one text-4).
--row-icon- PaintDefault var(--icon-2)
- Leading glyph colour (the current row is icon-1, a disabled one icon-4).
--row-description- PaintDefault var(--text-3)
- Description colour.
--row-meta- PaintDefault var(--text-3)
- Meta colour.
--row-cursor- "pointer" | "default"Default pointer
defaulton options the keyboard leads (a command palette).--glide-fill- PaintDefault var(--fill-4)
- The Glide's fill on this row under the pointer.
--glide-fill-kbd- PaintDefault 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. A label or description that fades is still read whole.
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" | "person"Default "md"
- Heights 24 / 28 / 30 (the sidebar row);
person, 52px (40px with nosub). leading- IconName | ReactNode
- 20×20 leading slot: an icon name, drawn beside the label, or a node (StatusDot, PineSweep). A
personrow's is its 36×36 avatar. reserveLeading- boolean
- Keep the empty leading slot's width, so the label lines up with its siblings.
label- ReactNode
- The label (or pass it as children). Text ends on a whole word; a Skeleton here keeps the row height.
labelId- string
forms- readonly (ReactNode | { cut: ReactNode })[]
- The label's shorter forms (default
textForms; a file namenameForms). description- ReactNode
- Text-3 after the label: it steps down, then goes, before the label loses a word; the hover says both whole.
descriptionForms- readonly (ReactNode | { cut: ReactNode })[]Default asideForms
- Its shorter forms.
descriptionYields- boolean
false: it carries state ("Unavailable while running") and keeps its seat while the label steps down.apart- number
- Beside a state description: its forms that tell it from its list's rows (
apartForms). full- false
false: the row's own Tooltip says the whole text.sub- ReactNode
personrows: one line under the name, text-3: the last thing said, or what they are doing now.subTone- "neutral" | "attention"Default "neutral"
personrows:attentiondrawssubin the warning ink (they wait for you).aside- ReactNode
personrows: a time ending the name's line; it yields to the name as a meta does.meta- ReactNode
- End slot (a time, a shortcut Badge, diff stats), hidden while actions show; never in the row's name: a shortcut (
Kbd,KbdGroup, a Badge,"⌘N") becomes itsaria-keyshortcuts, anything else its description. metaOnHover- boolean
- Quiet meta: hidden until hover or keyboard focus (a nav row's ⌘N badge).
metaYields- boolean
- The meta gives way to a label or description it would shorten until hover, focus or
highlighted(default;falsekeeps its seat). Not on apersonrow. mark- ReactNode
- A mark that never yields (a highlight) after the label, at the row's end after a cut one; a
personrow's ends its name's line. actions- ReactNode
- Hover-revealed end actions (IconButton size="xs" tone="muted") in
meta's slot; a destructive one goes in the row's menu. In a tree or listbox:aria-hidden, keyboard commands on the row. actionsOpen- boolean
- Keep actions and the hover fill while one's menu is open.
active- booleanDefault false
- Current item (route, open chat): fill + text-1. Set
aria-currentyourself where it applies. highlighted- booleanDefault false
- 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 (tray and palette rows with a description).
editing- booleanDefault false
- Inline rename: the row stops being a button so a
ListRowEditcan sit in the label slot. interactive- booleanDefault true
- 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, retiring: new code uses
rootStyle(ui.lintkit-classnamecounts the rest). disabled- booleanDefault false
- 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- booleanDefault true
- Focus and select the whole value on mount.
Rest props go to <input> props.