List view
A list of records: list views, search and a table to sort and select. Lightning's object home, for a standard or custom object.
Accounts
Select a list view10+ items • Sorted by Account Name • Filtered by All accounts • Updated a few seconds ago
| Row Number | Actions | ||||||
|---|---|---|---|---|---|---|---|
| 1 | Customer - Direct | Ellen Hart | Jun 12, 2026, 10:42 AM | ||||
| 2 | Customer - Channel | Ellen Hart | Jun 3, 2026, 1:10 PM | ||||
| 3 | Customer - Direct | Ellen Hart | Jun 12, 2026, 9:05 AM | ||||
| 4 | Customer - Channel | Priya Raman | May 29, 2026, 3:31 PM | ||||
| 5 | Customer - Channel | Priya Raman | Jun 10, 2026, 11:48 AM | ||||
| 6 | Customer - Direct | Ellen Hart | Jun 8, 2026, 8:22 AM | ||||
| 7 | Customer - Channel | Priya Raman | May 21, 2026, 4:15 AM | ||||
| 8 | Ellen Hart | Apr 30, 2026, 12:02 PM | |||||
| 9 | Customer - Direct | Ellen Hart | Jun 11, 2026, 2:40 PM | ||||
| 10 | Customer - Direct | Priya Raman | Jun 1, 2026, 10:55 PM |
Installation
The CLI installs the Radix or Base UI file to match your project, with what it builds on: data-table, list-view-header.
If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.
When to use
Use it
- For an object's tab in an app, standard or custom: its records with Lightning's list view switcher, search, row numbers, sortable columns, selection and a menu on each row.
- When you want Lightning's object home as a page you own: the Accounts page installs as a file, and another object's page is the same lines with its own columns, icon and label.
- When the app reads the records, a page at a time or all at once, and passes them in as plain rows.
Don't use it
- For one record's page. Use Record page.
- For the header alone, over a list of your own. Use List view header.
- For a few related records on a record page. Use Related list.
Usage
import { DataTable } from "@/components/wisty/data-table"
import { ListView, useListView } from "@/components/wisty/list-view"
import { ListViewHeader } from "@/components/wisty/list-view-header"const list = useListView({
records: cases,
searchBy: ["caseNumber", "subject", "status"],
columns,
getRowId: (record) => record.id,
loading,
})<ListView>
<ListViewHeader
icon="standard:case"
objectLabel="Cases"
views={views}
itemCount={list.itemCount}
sortedBy={list.sortedBy}
search={list.search}
onSearchChange={list.setSearch}
loading={loading}
joined
/>
<DataTable
table={list.table}
aria-label="All Open Cases"
isLoading={loading}
empty={list.unavailable ? "Cases aren't available." : undefined}
/>
</ListView>ListView is the frame: it joins a List view header to the Data table under it. useListView takes Data table's options with records in place of data, filters the records by the search over the searchBy fields, counts what's left, names the column the table sorts by, and tells rows that couldn't load from none. The Accounts page, ListViewPage, installs as list-view-page.tsx, the file you edit: swap its fixture import for the app's reads, and pass onAction, onRowAction and the handler of each list control the app supports. To search on the server, leave searchBy out, read list.search and pass the results in as records. To act on the selected rows, read list.table.getSelectedRowIds() beside the header's actions.
Examples
Custom object
Projects, a custom object, written out in full: its own icon, list views and columns, with a currency, a percent and empty values.
Projects
Select a list view9 items • Sorted by Project Name • Filtered by All projects • Updated a few seconds ago
| Row Number | Actions | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| 1 | Burlington Textiles Corp of America | Completed | $235,000.00 | 100% | Jul 6, 2026 | Ellen Hart | |||
| 2 | Dickenson plc | Planned | $15,000.00 | 0% | Jan 12, 2027 | Ellen Hart | |||
| 3 | Edge Communications | In Progress | $75,000.00 | 35% | Oct 5, 2026 | Ellen Hart | |||
| 4 | Edge Communications | Completed | $50,000.00 | 100% | Aug 17, 2026 | Priya Raman | |||
| 5 | Express Logistics and Transport | Completed | $220,000.00 | 100% | Jun 1, 2026 | Priya Raman | |||
| 6 | GenePoint | Planned | $60,000.00 | 0% | Ellen Hart | ||||
| 7 | In Progress | $12,000.00 | 50% | Oct 1, 2026 | Priya Raman | ||||
| 8 | Grand Hotels & Resorts Ltd | In Progress | $210,000.00 | 60% | Sep 14, 2026 | Marcus Lee | |||
| 9 | University of Arizona | On Hold | 20% | Aug 24, 2026 | Marcus Lee |
Unavailable
With `records` null, the count and the Updated time drop out and the table says so, where an empty array reads 0 items.
Accounts
Select a list viewSorted by Account Name • Filtered by All accounts
| Row Number | |||||
|---|---|---|---|---|---|
| Accounts aren't available. | |||||
API reference
ListView
The frame that joins a list view header to the table under it.
Also takes every <div> (opens in a new tab) prop.
useListViewhook
Holds the list's search and derives its count, sort label and unavailable state from the app's rows.
| Parameter | Type | Default |
|---|---|---|
options* | UseListViewOptions<TRecord> |
Returns UseListViewResult<TRecord>.
UseListViewOptionstype
| Member | Type | Default | Description |
|---|---|---|---|
records* | TRecord[] | null | The rows the app has read, or null when they couldn't load. | |
searchBy | readonly (keyof TRecord)[] | The fields the search matches; leave it out to search on the server. | |
loading | boolean | The first read: no count while it lasts, and never unavailable. | |
getRowId* | ( originalRow: TData, index: number, parent?: DataTableRow<TData> ) => string | A stable id per row, like a record id. Selection is kept by it. | |
showRowNumberColumn | boolean | Adds a row number column first; the numbers follow the rows shown, so sorting renumbers them. | |
rowNumberOffset | number | The rows before the first: with 10, the first row reads 11. Defaults to 0. | |
maxRowSelection | number | Caps the selection in every kind of change; 1 renders radios. Implies enableRowSelection. | |
getRowLabel | (row: Row<TFeatures, TData>) => string | Names a row in its labels, like "Select Acme". Defaults to the row header's value. | |
rowActions | | readonly DataTableRowAction[] | ((row: Row<TFeatures, TData>) => readonly DataTableRowAction[]) | The actions menu of each row, or a function that returns a row's actions when its menu opens. | |
onRowAction | (action: DataTableRowAction, row: Row<TFeatures, TData>) => void | Called with the action picked from a row's menu. | |
onHeaderAction | ( action: DataTableHeaderAction, column: Column<TFeatures, TData, unknown> ) => void | Called with the action picked from a column header's menu. | |
locale | string | A BCP 47 locale for numbers, dates and sorting text. Defaults to "en-US". | |
timeZone | string | An IANA time zone for "datetime" values. Defaults to "UTC". | |
now | string | number | Date | The instant "relative" values count from. Defaults to render time; pass one for a stable render. |
Also has every tanstack/table-core member.
UseListViewResulttype
| Member | Type | Default | Description |
|---|---|---|---|
table* | DataTableInstance<TRecord> | The table for DataTable, over the rows the search leaves. | |
search* | string | The search text, for ListViewHeader's search. | |
setSearch* | (search: string) => void | Sets the search text, for ListViewHeader's onSearchChange. | |
itemCount* | number | null | How many rows the search leaves; null while loading or unavailable. | |
sortedBy* | string | The label of the column sorted first, if any, for ListViewHeader's sortedBy. | |
unavailable* | boolean | The rows couldn't load: records is null and nothing is loading. |
SLDS mapping
The page is Lightning's object home: List view header (the page header's object home variant) joined to Data table in one frame, the way a list view draws its table under the header.
Known gaps:
- No kanban or split view: the page draws a table, so it passes no
onDisplayChange, and Display as doesn't show. - No inline edit in the cells, no list view charts and no filter panel.
Responsive behavior
- The frame takes its container's width. The header's controls move under the meta line when they don't fit, and the table scrolls sideways in a narrow container.
Rules
- Pass
nulltorecordswhen the list couldn't load, never[]: an empty array says the list view has no records. - Pass
loadingtouseListViewandListViewHeader, and asisLoadingtoDataTable, while the first rows are read. - While the app has read only some rows, pass
hasMoreItemsto the header andhasNextPageto the table. The count then reads "10+ items", and Load more asks for the rest. - Selection outlives a search that hides selected rows. Act only on the selected rows the table shows, or call
list.table.resetRowSelection()when the search changes. - Pass
updatedas the page writes it, from the time the rows were read, and leave it out whilelist.unavailable. The page never reads the clock. - Render one per page: the list view's name is the page's h1. Never pass
asChildorrender.