ListView
Show a collection of items as a list, with optional per-row controls.
A <ListView> shows a collection of items as rows in a list. Each row can carry its own controls, such as a <Button> or an <ActionMenu>, so users can act on an item without leaving the page. Rows can also be selectable, for a flow that gathers several items and then acts on all of them at once.
Use it for collections people work through, like a notification feed, a list of uploaded files, or the venues a user picks from in a dialog.
Anatomy
- List: The container that holds all rows.
- Item: A single row representing one entry of the collection.
- Selection indicator: The mark for a row's selected state, present only once the list has a
selectionMode. A checkbox for multiple selection, a radio circle for single. - Text value: The primary text of a row.
- Description: Optional secondary text below the text value, like a timestamp or a file size. One per row, and it takes inline markup, so several facts can share a line: "Updated 3 days ago · 2.1 MB".
- Actions: Optional trailing controls for the row, such as a button or a row menu.
Appearance
The appearance of a component can be customized using the variant and size props. These props adjust the visual style and dimensions of the component, available values are based on the active theme.
| Property | Type | Description |
|---|---|---|
variant | default | The available variants of this component. |
size | - | The available sizes of this component. |
Usage
Reach for a <ListView> when the user works through a collection one entry at a time, acting on a row where it sits instead of opening it. Typical cases:
- Notification and activity feeds: a stream of events the user scans, then dismisses or mutes per row.
- Resource lists: files, documents, or team members, each with rename, share, and delete behind a row menu.
- Connections and integrations: linked accounts or services the user reviews and disconnects individually.
A row is one item, not a record split into fields. A <ListView> gives you a text column and a trailing control, with nothing aligned between rows, so there is no way to scan a column or sort by one. Once the user needs to compare entries against each other (a status column, a size to sort by, dates to line up), reach for a <Table>.
Rows can be selected, though the selection stays in your own state rather than submitting with a form. ListView vs. SelectList below covers when that difference decides which one you want.
Content and layout
A row carries its primary text in a <TextValue>, an optional <Description> below it, and trailing controls. A bare string is shorthand for the text value.
Anything else the row shows, like a <Badge> or a status pill, belongs inside the text it describes rather than beside it. Left as a sibling it breaks the list's alignment: on its own it drops onto a line of its own under the description, and in a selectable list it pushes that row's text out of line with every other row.
<ListView.Item id="gasometer" textValue="Gasometer">
<TextValue>
Gasometer <Badge variant="warning">Sold out</Badge>
</TextValue>
<Description>Vienna · 1600 seats</Description>
</ListView.Item>Row actions
Show the 1 to 3 actions users reach for most often as icon buttons and group the rest behind an <ActionMenu>. Any more visible buttons turn the list into a wall of controls that competes with the text people are scanning.
Pick an icon that says what happens to the row, like Archive, Check, or BellOff. An X means "close this" everywhere else in Marigold, and a row's action doesn't close anything.
Deleting follows the same split as any other action: it goes in the menu, unless users do it often. Then it can stay visible as a destructive-ghost icon button, placed last. Either way, the Destructive actions pattern decides whether it asks for a confirmation or offers an undo.
When no single action stands out, or the available actions differ from row to row, drop the visible button and use the menu on its own.
Wrap two or more trailing controls in a <ButtonGroup>, or they stack on top of each other. Only buttons and menus belong there. A <Badge> or a status pill goes in the row's text, as shown under Content and layout above.
Selection
Make rows selectable when marking a row and acting on it are two separate steps. Two situations earn it:
- Staging one choice. The list is where the user picks a record, and something else commits it, like a venue chosen in a dialog and applied with its confirm button.
- Gathering rows for one operation. The user marks several records, then runs one action across all of them, like archiving six files at once, with an action bar to run it.
Leave selection off otherwise, which is the default. A row the user acts on where it sits doesn't need marking first, and an indicator on every row of a notification feed adds a step to a list that had none.
Whichever mode you pick, the selection is yours to keep. Hold it in your own state, read it through onSelectionChange, and decide when it commits. Nothing leaves the component until you act on it.
Single mode draws a radio circle, but a <ListView> is not a <Radio.Group>. If the rows are plain text and the choice belongs to a form, that is a radio group.
The selection is not always a Set
Cmd/Ctrl+A selects every row of a multi-select list and reports the string 'all' rather than a Set, whether or not the list shows a select-all control. Reading .size straight off it returns undefined, which surfaces as a wrong number in a confirmation rather than an error. Resolve the selection to concrete keys in one place before you count or act on it, the way the Bulk Actions pattern does for a <Table>.
Selecting and opening a row
One list can serve both browsing and selecting, so a user opens a file to check it and then marks several to archive without switching modes. Set onAction alongside selectionMode and one rule governs the press: it opens while nothing is selected, and selects once anything is.
Keep a clear button in reach on a list like this. While anything is selected there is no way to open a row, so a user who wants to go back to browsing needs somewhere obvious to undo the selection.
Enter does nothing while a selection exists
The keyboard follows the same rule as the press, with one gap. Space always marks the focused row, but Enter opens only while the selection is empty and does nothing at all once it is not, so a keyboard user gets no response and no hint why. Escape clears the selection and restores opening, which is why disallowEmptySelection should stay off on a list that sets onAction: it blocks Escape and strands them there. This is React Aria's toggle behaviour, shared with <Table> and not changeable by a prop.
Bulk actions
Pair a multi-select list with an <ActionBar>, driven by useActionBar. One thing is yours to do that a <Table> does for you: reserve the bar's height on the list's content, or the bar covers the last rows and keyboard navigation parks a focused row underneath it. A list short enough not to scroll needs none of that.
No select-all in a list
Select-all is a checkbox in the collection's header, and a list has no header row to hold one, so a flow where users regularly select every visible record belongs in a <Table>. Everything else in the Bulk Actions pattern applies to a <ListView> unchanged.
Empty state
Pass an <EmptyState> to the emptyState prop so a list with nothing to show still explains itself instead of rendering as a blank surface.
No resources yet
ListView vs. SelectList
On screen these two are hard to tell apart: the same stack of rich rows, an indicator on each, a description under the label. What differs is what the selection is for.
Reach for a <ListView> when you own the selection and decide yourself when it counts. Records staged in a dialog and applied with its confirm button, rows gathered for one bulk action, a feed where marking a row is a step in a flow you control. Reach for a <SelectList> when the selection is a field value, submitted and validated with the rest of the form, labelled and marked required while the user fills it in.
Two questions settle the cases that still look alike:
Does the selection submit with a form? If it does, it is a field value, and that is a <SelectList>.
Does the list need a visible label, helper text, or a validation message? If it does, it is a <SelectList> too, even for a pick that never submits. A <ListView> takes an aria-label and renders none of those, so a list that has to name itself on screen or explain a rule like "choose at least one" is a field whether or not a <form> is involved.
Underneath, one is a form field and the other a collection view. A <SelectList> carries a label, validation, and a hidden input, so its selection is the field's value and travels with the rest of the form. A <ListView> keeps its selection as state you own, and its rows can carry their own focusable controls, like a link or a row menu, which a field has no room for.
Do
Use <ListView> when you own the selection: a notification feed, a list
of files, or records staged in a dialog and committed with its button.
Don't
Don't use <ListView> for a choice that submits with a form. It has no
label, no validation, and no value, which is what <SelectList> is for.
Accessibility
- Up and Down move focus between rows. The list is a single tab stop, so
Rightmoves focus into the controls inside the focused row andLeftmoves it back out to the row.Tableaves the list. - Give the list an
aria-label, oraria-labelledbywhen a heading nearby already names it. - Set
textValueon a row whose text isn't a plain string, screen readers use it as the row's accessible name.
In a selectable list, moving focus never changes the selection. Space selects the focused row, Cmd/Ctrl+A selects every row in multiple mode, and Escape clears the whole selection, so a user who started selecting by accident has a way out. A press adds or removes a single row and never replaces the selection, while Shift+click and Shift+↑/↓ extend a range from the last marked row. A <Table> behaves the same way. The row itself announces whether it is selected, which is why the radio circle in single mode stays out of the accessibility tree: it says nothing a screen reader has not already heard.
<Title> normally renders a real heading. Inside a row it renders as a <span> instead, so a heading-like row doesn't add an entry to the page's heading outline.
Props
ListView
Prop
Type
Accessibility props (4)
Prop
Type
DOM event handlers (64)
Prop
Type
ListView.Item
Prop
Type
DOM event handlers (63)
Prop
Type
Alternative components
- List: Use for a static list of text with no per-row controls.
- SelectList: Use when the selection becomes a submitted form value.
- Select: Use for a compact single/multi-select form field.
- Radio: Use for a small set of mutually exclusive choices as a labelled form field, with plain text options rather than rich rows.
- Table: Use when each row is a record with multiple columns of structured data to compare, not a flat list of operable items.
- Menu: Use for a set of commands that close the surface on activation.
- Card: Use to present one item, like an event or a product, as a self-contained visual unit.