@shopify/polaris
Version:
Shopify’s admin product component library
1,525 lines (1,362 loc) • 40.5 kB
Markdown
---
name: Index table
category: Lists and tables
keywords:
- ResourceList
- index
- table
- list
- collections
- items
- objects
- list of products
- list of orders
- product lists
- order lists
- collections lists
- collection lists
- list of collections
- product listings list
- channel lists
- resource list attributes
- list attributes
- exceptions list
- list secondary actions
- secondary actions in a list
- list of resources
- filter
- sort
---
# Index table
An index table displays a collection of objects of the same type, like orders or products. The main job of an index table is to help merchants get an at-a-glance of the objects to perform actions or navigate to a full-page representation of it.
Index tables can also:
- Support [customized index rows and columns](https://polaris.shopify.com/components/lists-and-tables/resource-item)
- Include bulk actions so merchants can act on multiple objects at once
- Support sorting and [filtering](https://polaris.shopify.com/components/lists-and-tables/filters) of long lists
- Be paired with pagination to make long lists digestible
---
## Examples
### Simple index table
A index table with simple items and no bulk actions, sorting, or filtering.
```jsx
function SimpleIndexTableExample() {
const customers = [
{
id: '3411',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2561',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
}
```
### Simple small screen index table
A small screen index table with simple items and no bulk actions, sorting, or filtering.
```jsx
function SimpleSmallScreenIndexTableExample() {
const customers = [
{
id: '3412',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2562',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<div style={{padding: '12px 16px'}}>
<p>
<TextStyle variation="strong">{name}</TextStyle>
</p>
<p>{location}</p>
<p>{orders}</p>
<p>{amountSpent}</p>
</div>
</IndexTable.Row>
),
);
return (
<div style={{width: '430px'}}>
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
condensed
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
</div>
);
}
```
### IndexTable with empty state
Use to explain the purpose of a index table when no resources exist yet. This allows a smooth transition from a list in a loading state to a list where zero, one, or many resources exist.
```jsx
function IndexTableWithCustomEmptyStateExample() {
const customers = [];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const emptyStateMarkup = (
<EmptySearchResult
title={'No customers yet'}
description={'Try changing the filters or search term'}
withIllustration
/>
);
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
emptyState={emptyStateMarkup}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
}
```
### IndexTable with bulk actions
Allows merchants to select items and perform an action on the selection.
```jsx
function IndexTableWithBulkActionsExample() {
const customers = [
{
id: '3413',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2563',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const promotedBulkActions = [
{
content: 'Edit customers',
onAction: () => console.log('Todo: implement bulk edit'),
},
];
const bulkActions = [
{
content: 'Add tags',
onAction: () => console.log('Todo: implement bulk add tags'),
},
{
content: 'Remove tags',
onAction: () => console.log('Todo: implement bulk remove tags'),
},
{
content: 'Delete customers',
onAction: () => console.log('Todo: implement bulk delete'),
},
];
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
bulkActions={bulkActions}
promotedBulkActions={promotedBulkActions}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
}
```
### IndexTable with multiple promoted bulk actions
Allows merchants to select items and perform different actions on the selection.
```jsx
function IndexTableWithMultiplePromotedBulkActionsExample() {
const customers = [
{
id: '3413',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2563',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const promotedBulkActions = [
{
content: 'Capture payments',
onAction: () => console.log('Todo: implement payment capture'),
},
{
title: 'Edit customers',
actions: [
{
content: 'Add customers',
onAction: () => console.log('Todo: implement adding customers'),
},
{
content: 'Delete customers',
onAction: () => console.log('Todo: implement deleting customers'),
},
],
},
{
title: 'Export',
actions: [
{
content: 'Export as PDF',
onAction: () => console.log('Todo: implement PDF exporting'),
},
{
content: 'Export as CSV',
onAction: () => console.log('Todo: implement CSV exporting'),
},
],
},
];
const bulkActions = [
{
content: 'Add tags',
onAction: () => console.log('Todo: implement bulk add tags'),
},
{
content: 'Remove tags',
onAction: () => console.log('Todo: implement bulk remove tags'),
},
{
content: 'Delete customers',
onAction: () => console.log('Todo: implement bulk delete'),
},
];
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
bulkActions={bulkActions}
promotedBulkActions={promotedBulkActions}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
}
```
### IndexTable with bulk actions and selection across pages
Allows merchants to select items, perform an action on the selection and select resources across pages.
```jsx
function IndexTableWithBulkActionsAndSelectionAcrossPagesExample() {
const customers = [
{
id: '3414',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2564',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const promotedBulkActions = [
{
content: 'Edit customers',
onAction: () => console.log('Todo: implement bulk edit'),
},
];
const bulkActions = [
{
content: 'Add tags',
onAction: () => console.log('Todo: implement bulk add tags'),
},
{
content: 'Remove tags',
onAction: () => console.log('Todo: implement bulk remove tags'),
},
{
content: 'Delete customers',
onAction: () => console.log('Todo: implement bulk delete'),
},
];
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
hasMoreItems
bulkActions={bulkActions}
promotedBulkActions={promotedBulkActions}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
}
```
### IndexTable with loading state
Notifies merchants that index table items are being processed.
```jsx
function IndexTableWithLoadingExample() {
const customers = [
{
id: '3415',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2565',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
loading
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
}
```
### IndexTable with filtering
Allows merchants to narrow the index table to a subset of the original items.
```jsx
function IndexTableWithFilteringExample() {
const customers = [
{
id: '3416',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2566',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const [taggedWith, setTaggedWith] = useState('VIP');
const [queryValue, setQueryValue] = useState(null);
const [sortValue, setSortValue] = useState('today');
const handleTaggedWithChange = useCallback(
(value) => setTaggedWith(value),
[],
);
const handleTaggedWithRemove = useCallback(() => setTaggedWith(null), []);
const handleQueryValueRemove = useCallback(() => setQueryValue(null), []);
const handleClearAll = useCallback(() => {
handleTaggedWithRemove();
handleQueryValueRemove();
}, [handleQueryValueRemove, handleTaggedWithRemove]);
const handleSortChange = useCallback((value) => setSortValue(value), []);
const filters = [
{
key: 'taggedWith',
label: 'Tagged with',
filter: (
<TextField
label="Tagged with"
value={taggedWith}
onChange={handleTaggedWithChange}
autoComplete="off"
labelHidden
/>
),
shortcut: true,
},
];
const appliedFilters = !isEmpty(taggedWith)
? [
{
key: 'taggedWith',
label: disambiguateLabel('taggedWith', taggedWith),
onRemove: handleTaggedWithRemove,
},
]
: [];
const sortOptions = [
{label: 'Today', value: 'today'},
{label: 'Yesterday', value: 'yesterday'},
{label: 'Last 7 days', value: 'lastWeek'},
];
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<div style={{padding: '16px', display: 'flex'}}>
<div style={{flex: 1}}>
<Filters
queryValue={queryValue}
filters={filters}
appliedFilters={appliedFilters}
onQueryChange={setQueryValue}
onQueryClear={handleQueryValueRemove}
onClearAll={handleClearAll}
/>
</div>
<div style={{paddingLeft: '0.25rem'}}>
<Select
labelInline
label="Sort by"
options={sortOptions}
value={sortValue}
onChange={handleSortChange}
/>
</div>
</div>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
function disambiguateLabel(key, value) {
switch (key) {
case 'taggedWith':
return `Tagged with ${value}`;
default:
return value;
}
}
function isEmpty(value) {
if (Array.isArray(value)) {
return value.length === 0;
} else {
return value === '' || value == null;
}
}
}
```
### Index table with row status
An index table with rows differentiated by status.
```jsx
function IndexTableWithRowStatusExample() {
const customers = [
{
id: '3411',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
status: 'success',
},
{
id: '2561',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
status: 'subdued',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent, status}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
status={status}
>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
}
```
### Index table with sticky last column
An index table with a sticky last column that stays visible on scroll. The last heading will also be sticky if not hidden.
```jsx
function StickyLastCellIndexTableExample() {
const customers = [
{
id: '3411',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2561',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent', hidden: false},
]}
lastColumnSticky
>
{rowMarkup}
</IndexTable>
</Card>
);
}
```
### Index table without checkboxes
An index table without checkboxes and bulk actions.
```jsx
function IndexTableWithoutCheckboxesExample() {
const customers = [
{
id: '3411',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2561',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row id={id} key={id} position={index}>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent', hidden: false},
]}
selectable={false}
>
{rowMarkup}
</IndexTable>
</Card>
);
}
```
### IndexTable with all of its elements
Use as a broad example that includes most of the elements and props available to index table.
```jsx
function IndexTableWithAllElementsExample() {
const customers = [
{
id: '3417',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2567',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const [taggedWith, setTaggedWith] = useState('VIP');
const [queryValue, setQueryValue] = useState(null);
const [sortValue, setSortValue] = useState('today');
const handleTaggedWithChange = useCallback(
(value) => setTaggedWith(value),
[],
);
const handleTaggedWithRemove = useCallback(() => setTaggedWith(null), []);
const handleQueryValueRemove = useCallback(() => setQueryValue(null), []);
const handleClearAll = useCallback(() => {
handleTaggedWithRemove();
handleQueryValueRemove();
}, [handleQueryValueRemove, handleTaggedWithRemove]);
const handleSortChange = useCallback((value) => setSortValue(value), []);
const promotedBulkActions = [
{
content: 'Edit customers',
onAction: () => console.log('Todo: implement bulk edit'),
},
];
const bulkActions = [
{
content: 'Add tags',
onAction: () => console.log('Todo: implement bulk add tags'),
},
{
content: 'Remove tags',
onAction: () => console.log('Todo: implement bulk remove tags'),
},
{
content: 'Delete customers',
onAction: () => console.log('Todo: implement bulk delete'),
},
];
const filters = [
{
key: 'taggedWith',
label: 'Tagged with',
filter: (
<TextField
label="Tagged with"
value={taggedWith}
onChange={handleTaggedWithChange}
autoComplete="off"
labelHidden
/>
),
shortcut: true,
},
];
const appliedFilters = !isEmpty(taggedWith)
? [
{
key: 'taggedWith',
label: disambiguateLabel('taggedWith', taggedWith),
onRemove: handleTaggedWithRemove,
},
]
: [];
const sortOptions = [
{label: 'Today', value: 'today'},
{label: 'Yesterday', value: 'yesterday'},
{label: 'Last 7 days', value: 'lastWeek'},
];
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<TextStyle variation="strong">{name}</TextStyle>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<div style={{padding: '16px', display: 'flex'}}>
<div style={{flex: 1}}>
<Filters
queryValue={queryValue}
filters={filters}
appliedFilters={appliedFilters}
onQueryChange={setQueryValue}
onQueryClear={handleQueryValueRemove}
onClearAll={handleClearAll}
/>
</div>
<div style={{paddingLeft: '0.25rem'}}>
<Select
labelInline
label="Sort by"
options={sortOptions}
value={sortValue}
onChange={handleSortChange}
/>
</div>
</div>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
hasMoreItems
bulkActions={bulkActions}
promotedBulkActions={promotedBulkActions}
lastColumnSticky
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent', hidden: false},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
function disambiguateLabel(key, value) {
switch (key) {
case 'taggedWith':
return `Tagged with ${value}`;
default:
return value;
}
}
function isEmpty(value) {
if (Array.isArray(value)) {
return value.length === 0;
} else {
return value === '' || value == null;
}
}
}
```
### Small screen IndexTable with all of its elements
Use as a broad example that includes most of the elements and props available to index table.
```jsx
function SmallScreenIndexTableWithAllElementsExample() {
const customers = [
{
id: '3418',
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2568',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const [taggedWith, setTaggedWith] = useState('VIP');
const [queryValue, setQueryValue] = useState(null);
const [sortValue, setSortValue] = useState('today');
const handleTaggedWithChange = useCallback(
(value) => setTaggedWith(value),
[],
);
const handleTaggedWithRemove = useCallback(() => setTaggedWith(null), []);
const handleQueryValueRemove = useCallback(() => setQueryValue(null), []);
const handleClearAll = useCallback(() => {
handleTaggedWithRemove();
handleQueryValueRemove();
}, [handleQueryValueRemove, handleTaggedWithRemove]);
const handleSortChange = useCallback((value) => setSortValue(value), []);
const promotedBulkActions = [
{
content: 'Edit customers',
onAction: () => console.log('Todo: implement bulk edit'),
},
];
const bulkActions = [
{
content: 'Add tags',
onAction: () => console.log('Todo: implement bulk add tags'),
},
{
content: 'Remove tags',
onAction: () => console.log('Todo: implement bulk remove tags'),
},
{
content: 'Delete customers',
onAction: () => console.log('Todo: implement bulk delete'),
},
];
const filters = [
{
key: 'taggedWith',
label: 'Tagged with',
filter: (
<TextField
label="Tagged with"
value={taggedWith}
onChange={handleTaggedWithChange}
autoComplete="off"
labelHidden
/>
),
shortcut: true,
},
];
const appliedFilters = !isEmpty(taggedWith)
? [
{
key: 'taggedWith',
label: disambiguateLabel('taggedWith', taggedWith),
onRemove: handleTaggedWithRemove,
},
]
: [];
const sortOptions = [
{label: 'Today', value: 'today'},
{label: 'Yesterday', value: 'yesterday'},
{label: 'Last 7 days', value: 'lastWeek'},
];
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<div style={{padding: '.75rem 1rem'}}>
<p>
<TextStyle variation="strong">{name}</TextStyle>
</p>
<p>{location}</p>
<p>{orders}</p>
<p>{amountSpent}</p>
</div>
</IndexTable.Row>
),
);
return (
<div style={{width: '430px'}}>
<Card>
<div style={{padding: '16px', display: 'flex'}}>
<div style={{flex: 1}}>
<Filters
queryValue={queryValue}
filters={filters}
appliedFilters={appliedFilters}
onQueryChange={setQueryValue}
onQueryClear={handleQueryValueRemove}
onClearAll={handleClearAll}
/>
</div>
<div style={{paddingLeft: '0.25rem'}}>
<Select
labelInline
label="Sort by"
options={sortOptions}
value={sortValue}
onChange={handleSortChange}
/>
</div>
</div>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
hasMoreItems
condensed
bulkActions={bulkActions}
promotedBulkActions={promotedBulkActions}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
</div>
);
function disambiguateLabel(key, value) {
switch (key) {
case 'taggedWith':
return `Tagged with ${value}`;
default:
return value;
}
}
function isEmpty(value) {
if (Array.isArray(value)) {
return value.length === 0;
} else {
return value === '' || value == null;
}
}
}
```
---
## Build
Using an index table in a project involves combining the following components and subcomponents:
- IndexTable
- [IndexTableRow](#index-table-row)
- [IndexTableCell](#index-table-cell)
- [Filters](https://polaris.shopify.com/components/lists-and-tables/filters) (optional)
- Pagination component (optional)
<!-- hint -->
The index table component provides the UI elements for list sorting, filtering, and pagination, but doesn’t provide the logic for these operations. When a sort option is changed, filter added, or second page requested, you’ll need to handle that event (including any network requests) and then update the component with new props.
<!-- end -->
---
## Purpose
Shopify is organized around objects that represent merchants businesses, like customers, products, and orders. Each individual order, for example, is given a dedicated page that can be linked to. In Shopify, we call these types of objects _resources_, and we call the object’s dedicated page its _details page_.
### Problem
Take orders as an example. Merchants may have a lot of them. They need a way to scan their orders, view the different attributes on each order, and find out which ones need action first. In other words, they need a way find an individual order, call up more information about it, and take action on it.
### Solution
Index tables function as:
- A content format, presenting a set of individual resources with multiple columns of information for each
- A system for taking action on one or more individual resources
- A way to navigate to an individual resource’s details page
Because a details page displays all the content and actions for an individual resource, you can think of a resource list as a summary of these details pages. In this way resource lists bridge a middle level in Shopify’s navigation hierarchy.
---
## Best practices
Index tables should:
- Have items that perform an action when clicked. The action should navigate to the resource’s details page or otherwise provide more detail about the item.
- [Customize the content and layout](https://polaris.shopify.com/components/lists-and-tables/resource-item) of their items rows to surface information to support merchants’ needs.
- Support sorting if the list can be long, and especially if different merchant tasks benefit from different sort orders.
- Support [filtering](https://polaris.shopify.com/components/lists-and-tables/filters) if the list can be long.
- Paginate when the current list contains more than 50 items.
- Use the [skeleton page](https://polaris.shopify.com/components/feedback-indicators/skeleton-page) component on initial page load for the rest of the page if the loading prop is true and items are processing.
Index tables can optionally:
- Provide bulk actions for tasks that are often applied to many list items at once. For example, merchants may want to add the same tag to a large number of products.
---
## Content guidelines
Index tables should:
- Identify the type of resource, usually with a heading
<!-- usagelist -->
#### Do
- Products
- Showing 50 products
#### Don’t
- _No heading_
<!-- end -->
- Indicate when not all members of a resource are being shown. For a card summarizing and linking to recently purchased products:
<!-- usagelist -->
#### Do
- Popular products this week
#### Don’t
- Products
<!-- end -->
- Follow the verb + noun formula for bulk actions
- Follow the [content guidelines for filter options and applied filters](https://polaris.shopify.com/components/lists-and-tables/filters#section-content-guidelines)
---
<a name="index-table-row"></a>
## IndexTableRow
An `IndexTableRow` is used to render a row representing an item within an `IndexTable`
### IndexTableRow properties
| Prop | Type | Description |
| -------- | --------- | --------------------------------------------------------------- |
| id | string | A unique identifier for the row |
| selected | boolean | A boolean property indicating whether the row is selected |
| position | number | The index position of the row |
| subdued | boolean | A boolean property indicating whether the row should be subdued |
| status | RowStatus | A property indicating whether the row should have a status |
<a name="index-table-cell"></a>
## IndexTableCell
An `IndexTableCell` is used to render a single cell within an `IndexTableRow`
### IndexTableCell properties
| Prop | Type | Description |
| ----- | ------- | -------------------------------------------------------------------------------- |
| flush | boolean | A boolean property indicating whether the cell should remove the default padding |
---
## Related components
- To create an actionable list of related items that link to details pages, such as a list of customers, use the [resource list component](https://polaris.shopify.com/components/lists-and-tables/resource-list)
- To present structured data for comparison and analysis, like when helping merchants to gain insights or review analytics, use the [data table component](https://polaris.shopify.com/components/lists-and-tables/data-table)
- To display a simple list of related content, [use the list component](https://polaris.shopify.com/components/lists-and-tables/list)