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, but the selection stays in your own state and never submits with a form. That is the line between a <ListView> and a <SelectList>, and the decision test below is how you pick between them.
Row text
Author a row's primary text with <TextValue>, or pass a bare string. Secondary text goes in a <Description>, one per row.
To emphasise part of a row's text, nest <Text as="span"> inside <TextValue> or <Description> rather than adding a <Text> of its own:
<ListView.Item id="deploy" textValue="Deploy succeeded">
<TextValue>
Deploy{' '}
<Text as="span" weight="bold">
succeeded
</Text>
</TextValue>
</ListView.Item>The same rule covers everything else a row carries. A row has a cell for its text, a cell for its trailing control, and, once the list is selectable, a cell for its selection indicator. A child that claims none of them, like a <Badge>, gets placed wherever the grid has room. On its own that means a new full-width line under the description, where it reads as part of the row's text. In a selectable list it is worse: the badge lands in the indicator column and widens it, so that row's text no longer lines up with any other row's. Nest it in the text it belongs to instead:
<ListView.Item id="gasometer" textValue="Gasometer">
<TextValue>
Gasometer <Badge variant="warning">Sold out</Badge>
</TextValue>
<Description>Vienna · 1600 seats</Description>
</ListView.Item>Row actions
Keep the action users reach for most often visible as an icon button and group the rest behind an <ActionMenu>. More than two visible buttons per row turns the list into a wall of controls that competes with the text people are scanning.
Pick a glyph 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. Rare or irreversible commands like deleting belong in the menu, as an <ActionMenu.Item variant="destructive">, not behind a one-click icon button.
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.
A row has a single cell for trailing controls, so wrap two or more in a <ButtonGroup>. Left loose, they claim the same cell and stack. That cell takes Button-family controls only: a <Badge> or a status pill belongs in the row's text, as shown under Row text above.
Selection
Make rows selectable when the user's next step needs a row they haven't acted on yet. 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. Set
selectionMode="single". - Gathering rows for one operation. The user marks several records, then runs one action across all of them, like archiving six files at once. Set
selectionMode="multiple"and give them 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. It never reaches a surrounding <form>, so a choice that has to submit and validate with the rest of the form is a <SelectList> instead.
Rows are marked one at a time. A press adds or removes a single row, and there is no Shift+click to take a range, in a <ListView> or in a <Table>. Plan a flow that depends on range selection around that.
Selecting and opening a row
A selectable list can still open its rows. Set onAction alongside selectionMode and the press does whichever the user is more likely to mean:
- Nothing selected: pressing a row opens the item, and Enter does the same. The checkbox and Space select without opening anything.
- Something selected: pressing a row marks it instead, and nothing opens. Space marks the focused row, and Enter does nothing at all.
One list serves browsing and selecting this way, without a mode switch of your own to build. The catch is the second state: while anything is selected there is no way to open a row, and a keyboard user who presses Enter gets no response of any kind. Escape clears the selection and opening works again, so keep a clear button within reach rather than leaving that as the only way out.
Bulk actions
Pair a multi-select list with an <ActionBar>, rendered as a sibling inside the list's scroll container. The bar is sticky, so it pins itself to the bottom of that container and appears once something is selected.
Drive it with useActionBar, which holds the selection, fills in the count and the clear button, and measures the bar. You need that measurement: a <ListView> does not know the bar is there, so without it the bar covers the last rows and keyboard navigation parks a focused row underneath it. Reserve its height on the scroll container in both padding-bottom and scroll-padding-bottom, which is what a <Table> does for you.
A list short enough not to scroll needs none of that. Render the <ActionBar> after it and pass selectedItemCount and onClearSelection yourself.
One piece of the Bulk Actions pattern is missing here: select-all. That 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 pattern applies to a <ListView> unchanged, including bounding the selection to the rows the user can see, clearing it when a filter or a page changes what is visible, and confirming a destructive action with the exact count.
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 where the selection goes, and one question settles it:
Does the selection need to submit with the form?
- Yes, it is a field value that submits and validates:
<SelectList>.- No, you read it and commit it yourself:
<ListView>.
One exception runs the other way. A pick that never submits but still needs a visible label, helper text, or a validation message wants a <SelectList> anyway, because a <ListView> renders none of those. It takes an aria-label and nothing else, 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.
That is the real distinction, and a poor decision aid on its own, because most flows can be described either way. Start from the question.
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
- Arrow keys move focus between rows,
Tabreaches the controls inside the focused row. - 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 and Escape clears the whole selection, so a user who started selecting by accident has a way out. 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.
- 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.