@hackplan/polaris
Version:
Shopify’s product component library
1,745 lines (1,417 loc) • 102 kB
Markdown
---
name: Resource list
category: Lists and tables
keywords:
- ResourceList
- 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
---
# Resource list
A resource list displays a collection of objects of the same type, like products or customers. The main job of a resource list is to help merchants find an object and navigate to a full-page representation of it.
<div class="TypeContainerImage TypeContainerImage--PageBackground">

</div>
Resource lists can also:
- Support [customized list items](#study-custom-item)
- Include [bulk actions](#study-bulk-actions) so merchants can act on multiple objects at once
- Support [sorting](#study-sorting) and filtering [filtering](#study-filtering) of long lists
- Be paired with [pagination](#study-pagination) to make long lists digestible
---
## Examples
### Simple resource list
A resource list with simple items and no bulk actions, sorting, or filtering. See the [case study below](#study) for implementation details.
```jsx
<Card>
<ResourceList
resourceName={{singular: 'customer', plural: 'customers'}}
items={[
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
]}
renderItem={(item) => {
const {id, url, name, location} = item;
const media = <Avatar customer size="medium" name={name} />;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
}}
/>
</Card>
```
### Simple resource list with drag and drop
A resource list with simple items and no bulk actions, sorting, or filtering. See the [case study below](#study) for implementation details.
```jsx
class ResourceListExample extends React.Component {
state = {
items: [
{
id: 341,
url: '',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: 256,
url: '',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
]
}
render() {
return (
<Card>
<ResourceList
resourceName={{singular: 'customer', plural: 'customers'}}
items={this.state.items}
renderItem={(item, xxx, index) => {
const {id, url, name, location} = item;
const media = <Avatar customer size="medium" name={name} />;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
}}
onDragEnd={(result) => {
// dropped outside the list
if (!result.destination) {
return;
}
const {items} = this.state;
items.splice(result.destination.index, 0, items.splice(result.source.index, 1)[0]);
this.setState({
items
});
}}
/>
</Card>
)
}
}
```
### Resource list with bulk actions
Allows merchants to select items and perform an action on the selection. See [the bulk actions section of the case study](#study-bulk-actions) for implementation details.
```jsx
class ResourceListExample extends React.Component {
state = {
selectedItems: [],
};
handleSelectionChange = (selectedItems) => {
this.setState({selectedItems});
};
renderItem = (item) => {
const {id, url, name, location} = item;
const media = <Avatar customer size="medium" name={name} />;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
};
render() {
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const items = [
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
];
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'),
},
];
return (
<Card>
<ResourceList
resourceName={resourceName}
items={items}
renderItem={this.renderItem}
selectedItems={this.state.selectedItems}
onSelectionChange={this.handleSelectionChange}
promotedBulkActions={promotedBulkActions}
bulkActions={bulkActions}
/>
</Card>
);
}
}
```
### Resource list with loading state
Notifies merchants that list items are being processed.
```jsx
class ResourceListExample extends React.Component {
state = {
selectedItems: [],
};
handleSelectionChange = (selectedItems) => {
this.setState({selectedItems});
};
renderItem = (item) => {
const {id, url, name, location} = item;
const media = <Avatar customer size="medium" name={name} />;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
};
render() {
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const items = [
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
];
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'),
},
];
return (
<Card>
<ResourceList
resourceName={resourceName}
items={items}
renderItem={this.renderItem}
selectedItems={this.state.selectedItems}
onSelectionChange={this.handleSelectionChange}
promotedBulkActions={promotedBulkActions}
bulkActions={bulkActions}
loading={true}
/>
</Card>
);
}
}
```
### Resource list with sorting
Allows merchants to change the way the list is sorted by selecting one of several options from a [Select](/components/forms/select) control. See the [the sorting section of the case study](#study-sorting) for implementation details.
```jsx
class ResourceListExample extends React.Component {
state = {
sortValue: 'DATE_MODIFIED_DESC',
};
handleSortChange = (sortValue) => {
this.setState({sortValue});
};
renderItem = (item) => {
const {id, url, name, location} = item;
const media = <Avatar customer size="medium" name={name} />;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
};
render() {
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const items = [
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
];
return (
<Card>
<ResourceList
resourceName={resourceName}
items={items}
renderItem={this.renderItem}
sortValue={this.state.sortValue}
sortOptions={[
{label: 'Newest update', value: 'DATE_MODIFIED_DESC'},
{label: 'Oldest update', value: 'DATE_MODIFIED_ASC'},
]}
onSortChange={(selected) => {
this.setState({sortValue: selected});
console.log(`Sort option changed to ${selected}.`);
}}
/>
</Card>
);
}
}
```
### Resource list with alternate tool
Allows merchants to add an alternate tool in the current sort option location when sort may not be the most relevant action for the current list.
```jsx
class ResourceListExample extends React.Component {
renderItem = (item) => {
const {id, url, name, location} = item;
const media = <Avatar customer size="medium" name={name} />;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
};
render() {
const resourceName = {
singular: 'Customer',
plural: 'Customers',
};
const items = [
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
];
return (
<Card>
<ResourceList
items={items}
renderItem={this.renderItem}
resourceName={resourceName}
alternateTool={<Button>Email customers</Button>}
/>
</Card>
);
}
}
```
### Resource list with filtering
Allows merchants to narrow the resource list to a subset of the original items. See the [filter control subcomponent](#subcomponent-filter-control) and the [filtering section of the case study](#study-filtering) for implementation details.
```jsx
class ResourceListExample extends React.Component {
state = {
searchValue: '',
appliedFilters: [
{
key: 'accountStatusFilter',
value: 'Account enabled',
},
],
};
handleSearchChange = (searchValue) => {
this.setState({searchValue});
};
handleFiltersChange = (appliedFilters) => {
this.setState({appliedFilters});
};
renderItem = (item) => {
const {id, url, name, location} = item;
const media = <Avatar customer size="medium" name={name} />;
return (
<ResourceList.Item id={id} url={url} media={media}>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
};
render() {
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const items = [
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
];
const filters = [
{
key: 'orderCountFilter',
label: 'Number of orders',
operatorText: 'is greater than',
type: FilterType.TextField,
},
{
key: 'accountStatusFilter',
label: 'Account status',
operatorText: 'is',
type: FilterType.Select,
options: ['Enabled', 'Invited', 'Not invited', 'Declined'],
},
];
const filterControl = (
<ResourceList.FilterControl
filters={filters}
appliedFilters={this.state.appliedFilters}
onFiltersChange={this.handleFiltersChange}
searchValue={this.state.searchValue}
onSearchChange={this.handleSearchChange}
additionalAction={{
content: 'Save',
onAction: () => console.log('New filter saved'),
}}
/>
);
return (
<Card>
<ResourceList
resourceName={resourceName}
items={items}
renderItem={this.renderItem}
filterControl={filterControl}
/>
</Card>
);
}
}
```
### Resource list with item shortcut actions
Shortcut actions are intended to provide quick access to popular actions from the resource’s details page. They are shown when the mouse is hovered over the list item, and are not shown on small screen devices, so the action must also be accessible in another way. See the [Adding shortcut actions to resource list items section of the case study](#study-custom-item-shortcut-actions) for implementation details.
```jsx
<Card>
<ResourceList
resourceName={{singular: 'customer', plural: 'customers'}}
items={[
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
latestOrderUrl: 'orders/1456',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
latestOrderUrl: 'orders/1457',
},
]}
renderItem={(item) => {
const {id, url, name, location, latestOrderUrl} = item;
const media = <Avatar customer size="medium" name={name} />;
const shortcutActions = latestOrderUrl
? [{content: 'View latest order', url: latestOrderUrl}]
: null;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
shortcutActions={shortcutActions}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
}}
/>
</Card>
```
### Resource list with persistent item shortcut actions
Use persistent shortcut actions in rare cases when the action cannot be made available on the item’s details page. Persistent shortcut actions roll up into an overflow menu on small screens.
```jsx
<Card>
<ResourceList
resourceName={{singular: 'customer', plural: 'customers'}}
items={[
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
latestOrderUrl: 'orders/1456',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
latestOrderUrl: 'orders/1457',
},
]}
renderItem={(item) => {
const {id, url, name, location, latestOrderUrl} = item;
const media = <Avatar customer size="medium" name={name} />;
const shortcutActions = latestOrderUrl
? [{content: 'View latest order', url: latestOrderUrl}]
: null;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
shortcutActions={shortcutActions}
persistActions
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
}}
/>
</Card>
```
### Resource list with multiselect
Allows merchants to select or deselect multiple items at once.
```jsx
class ResourceListExample extends React.Component {
state = {
selectedItems: [],
};
handleSelectionChange = (selectedItems) => {
this.setState({selectedItems});
};
renderItem = (item, _, index) => {
const {id, url, name, location} = item;
const media = <Avatar customer size="medium" name={name} />;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
sortOrder={index}
accessibilityLabel={`View details for ${name}`}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
};
render() {
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const items = [
{
id: 231,
url: 'customers/231',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: 246,
url: 'customers/246',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
{
id: 276,
url: 'customers/276',
name: 'Joe Smith',
location: 'Arizona, USA',
},
{
id: 349,
url: 'customers/349',
name: 'Haden Jerado',
location: 'Decatur, USA',
},
{
id: 419,
url: 'customers/419',
name: 'Tom Thommas',
location: 'Florida, USA',
},
{
id: 516,
url: 'customers/516',
name: 'Emily Amrak',
location: 'Texas, USA',
},
];
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'),
},
];
return (
<Card>
<ResourceList
resourceName={resourceName}
items={items}
renderItem={this.renderItem}
selectedItems={this.state.selectedItems}
onSelectionChange={this.handleSelectionChange}
promotedBulkActions={promotedBulkActions}
bulkActions={bulkActions}
resolveItemId={resolveItemIds}
/>
</Card>
);
}
}
function resolveItemIds({id}) {
return id;
}
```
### Resource list with all of its elements
Use as a broad example that includes most props available to resource list.
```jsx
class ResourceListExample extends React.Component {
state = {
selectedItems: [],
sortValue: 'DATE_MODIFIED_DESC',
searchValue: '',
appliedFilters: [
{
key: 'accountStatusFilter',
value: 'Account enabled',
},
],
};
handleSearchChange = (searchValue) => {
this.setState({searchValue});
};
handleFiltersChange = (appliedFilters) => {
this.setState({appliedFilters});
};
handleSortChange = (sortValue) => {
this.setState({sortValue});
};
handleSelectionChange = (selectedItems) => {
this.setState({selectedItems});
};
renderItem = (item) => {
const {id, url, name, location, latestOrderUrl} = item;
const media = <Avatar customer size="medium" name={name} />;
const shortcutActions = latestOrderUrl
? [{content: 'View latest order', url: latestOrderUrl}]
: null;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
shortcutActions={shortcutActions}
persistActions
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
};
render() {
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const items = [
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
latestOrderUrl: 'orders/1456',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
latestOrderUrl: 'orders/1457',
},
];
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: 'orderCountFilter',
label: 'Number of orders',
operatorText: 'is greater than',
type: FilterType.TextField,
},
{
key: 'accountStatusFilter',
label: 'Account status',
operatorText: 'is',
type: FilterType.Select,
options: ['Enabled', 'Invited', 'Not invited', 'Declined'],
},
];
const filterControl = (
<ResourceList.FilterControl
filters={filters}
appliedFilters={this.state.appliedFilters}
onFiltersChange={this.handleFiltersChange}
searchValue={this.state.searchValue}
onSearchChange={this.handleSearchChange}
additionalAction={{
content: 'Save',
onAction: () => console.log('New filter saved'),
}}
/>
);
return (
<Card>
<ResourceList
resourceName={resourceName}
items={items}
renderItem={this.renderItem}
selectedItems={this.state.selectedItems}
onSelectionChange={this.handleSelectionChange}
promotedBulkActions={promotedBulkActions}
bulkActions={bulkActions}
sortValue={this.state.sortValue}
sortOptions={[
{label: 'Newest update', value: 'DATE_MODIFIED_DESC'},
{label: 'Oldest update', value: 'DATE_MODIFIED_ASC'},
]}
onSortChange={(selected) => {
this.setState({sortValue: selected});
console.log(`Sort option changed to ${selected}.`);
}}
filterControl={filterControl}
/>
</Card>
);
}
}
```
---
## Build
Using a resource list in a project involves combining the following components and subcomponents:
- ResourceList
- [ResourceList.Item](#subcomponent-item) or a [customized list item](#study-custom-item)
- [ResourceList.FilterControl](#subcomponent-filter-control) (optional)
- Pagination component (optional)
<!-- hint -->
The resource list 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 -->
View the [case study](#study) for a walkthrough of how to use this component to build an list page for customers.
---
## 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, see what state they’re in 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
Resource lists function as:
- A content format, presenting a set of individual resources in a compact form
- 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.
<div class="TypeContainerImage TypeContainerImage--PageBackground">

</div>
<!-- hint -->
#### Hint
#### A resource list isn’t a data table
On wide screens, a resource list often looks like a table, especially if some content is aligned in columns. Despite this, resource lists and data tables have different purposes.
A data table is a form of data visualization. It works best to present highly structured data for comparison and analysis.
If your use case is more about visualizing or analyzing data, use the [data table component](/components/lists-and-tables/data-table). If your use case is more about finding and taking action on objects, use a resource list.
<!-- end -->
---
## Best practices
Resource lists can live in many places in Shopify. You could include a short resource list in a card summarizing recent marketing activities. You could also dedicate an entire page to a resource list like Shopify’s main products list.
Resource lists 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](#study-custom-item) of their list items to support merchants’ needs.
- Support [sorting](#study-sorting) if the list can be long, and especially if different merchant tasks benefit from different sort orders.
- Support [filtering](#study-filtering) if the list can be long.
- [Paginate](#study-pagination) when the current list contains more than 50 items.
- Use the [skeleton page](/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.
Resource lists can optionally:
- Provide [bulk actions](#study-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
Resource lists 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](#study-bulk-actions-content-guidelines)
- Follow the [content formula for sort options](#study-sorting-content-guidelines)
- Follow the [content guidelines for filter options and applied filters](#study-filtering-content-guidelines)
---
<a name="subcomponent-item"></a>
## Resource list item
The content of a resource list consists of resource list items. Each item summarizes an individual resource and should link to its details page.
Because the content of items depends on the type of resource and merchant tasks, resource list items are flexible.
See the case study section for [more about customizing and using resource list items](#study-custom-item).
<div class="TypeContainerImage TypeContainerImage--PageBackground">

</div>
<a name="subcomponent-item-examples"></a>
### Item examples
#### Simple resource list item
A basic resource list item with its details filled in at the point of use.
<div class="TypeContainerImage TypeContainerImage--PageBackground">

</div>
```jsx
<ResourceList
resourceName={{singular: 'blog post', plural: 'blog posts'}}
items={[
{
id: 6,
url: 'posts/6',
title: 'How To Get Value From Wireframes',
author: 'Jonathan Mangrove',
},
]}
renderItem={(item) => {
const {id, url, title, author} = item;
const authorMarkup = author ? <div>by {author}</div> : null;
return (
<ResourceList.Item
id={id}
url={url}
accessibilityLabel={`View details for ${title}`}
>
<h3>
<TextStyle variation="strong">{title}</TextStyle>
</h3>
{authorMarkup}
</ResourceList.Item>
);
}}
/>
```
#### Item with media
The media element can hold an [avatar](/components/images-and-icons/avatar), [thumbnail](/components/images-and-icons/thumbnail) or other small-format graphic.
<div class="TypeContainerImage TypeContainerImage--PageBackground">

</div>
```jsx
<ResourceList
resourceName={{singular: 'customer', plural: 'customers'}}
items={[
{
id: 145,
url: 'customers/145',
avatarSource: 'https://avatars.io/twitter/Astro_Soyeon',
name: 'Yi So-Yeon',
location: 'Gwangju, South Korea',
},
]}
renderItem={(item) => {
const {id, url, avatarSource, name, location} = item;
return (
<ResourceList.Item
id={id}
url={url}
media={
<Avatar customer size="medium" name={name} source={avatarSource} />
}
accessibilityLabel={`View details for ${name}`}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
}}
/>
```
#### Item with shortcut actions
Shortcut actions present popular actions from the resource’s details page for easy access.
<div class="TypeContainerImage TypeContainerImage--PageBackground">

</div>
```jsx
<ResourceList
resourceName={{singular: 'customer', plural: 'customers'}}
items={[
{
id: 145,
url: 'customers/145',
avatarSource: 'https://avatars.io/twitter/Astro_Soyeon',
name: 'Yi So-Yeon',
location: 'Gwangju, South Korea',
latestOrderUrl: 'orders/1456',
},
]}
renderItem={(item) => {
const {id, url, avatarSource, name, location, latestOrderUrl} = item;
const shortcutActions = latestOrderUrl
? [{content: 'View latest order', url: latestOrderUrl}]
: null;
return (
<ResourceList.Item
id={id}
url={url}
media={
<Avatar customer size="medium" name={name} source={avatarSource} />
}
shortcutActions={shortcutActions}
accessibilityLabel={`View details for ${name}`}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
}}
/>
```
<a name="subcomponent-item-props"></a>
### Item properties
| Prop | Type | Description |
| -------------------- | -------------------------- | ---------------------------------------------------------------------------------- |
| id\* | string | Unique identifier for the item within the list |
| url | string | URL for the resource’s details page (required unless \`onClick\` is provided) |
| accessibilityLabel\* | string | Accessibility label for item link |
| ariaControls | string | Id of the element the item onClick controls |
| ariaExpanded | string | Tells screen reader the controlled element is expanded |
| onClick | function(id: string): void | Callback when clicked (required if \`url\` is omitted) |
| media | React.reactNode | Content for the media area at the left of the item, usually an Avatar or Thumbnail |
| children | React.reactNode | Content for the details area |
| shortcutActions | DisableableAction[] | 1 or 2 shortcut actions; must be available on the page linked to by \`url\` |
| persistActions | boolean | Makes the shortcut actions always visible |
<a name="subcomponent-item-best-practices"></a>
### Item best practices
Resource list items should:
- Perform an action when clicked. The action should navigate to the resource’s details page or otherwise provide more detail about the item.
- Be tailored to the specific type of resource being displayed.
- Lay out the content effectively across all screen sizes.
Resource list items can optionally:
- Use [conditional content](#study-custom-item-conditional-content) to help merchants deal with items in unusual states
- Provide [shortcut actions](#study-custom-item-shortcut-actions) for quick access to frequent actions from the resource’s details page
Read the [case study](#study-custom-item) to see how the best practices are applied.
<a name="subcomponent-item-content-guidelines"></a>
### Item content guidelines
Resource list items should:
- Present the content merchants need to find the items they’re looking for.
- Support merchants’ tasks for the particular type of resource.
- Present content elements concisely. For example, add a label or clarifying phrase only when necessary.
- Avoid truncating content where possible.
- Avoid colons.
- [Conditional actions](#study-custom-item-conditional-content) should follow the verb + noun content formula for buttons.
- If a content value is empty, don’t use an em dash (“—”) like in a table. Instead, use a phrase like “No orders.”
- [Shortcut actions](#study-custom-item-shortcut-actions) don’t need to follow the full verb + noun formula for buttons.
See the [case study](#study-custom-item) for content guidelines in action.
---
<a name="subcomponent-filter-control"></a>
## Resource list filter control
Provides a default interface for adding and removing filters. Supports quick filtering using a text field. A more advanced filter builder can be accessed from a popover. Applied filters are represented as removeable tags.
<div class="TypeContainerImage TypeContainerImage--PageBackground">

</div>
<a name="subcomponent-filter-control-examples"></a>
### Filter control examples
#### Resource list with filter control
Filter control showing a state with applied filters and an additional action (optional).
<div class="TypeContainerImage">

</div>
```jsx
<ResourceList
resourceName={{singular: 'customer', plural: 'customers'}}
items={[
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
]}
renderItem={(item) => {
const {id, url, name, location} = item;
const media = <Avatar customer size="medium" name={name} />;
return (
<ResourceList.Item
id={id}
url={url}
media={media}
accessibilityLabel={`View details for ${name}`}
>
<h3>
<TextStyle variation="strong">{name}</TextStyle>
</h3>
<div>{location}</div>
</ResourceList.Item>
);
}}
filterControl={
<ResourceList.FilterControl
filters={[
{
key: 'orderCountFilter',
label: 'Number of orders',
operatorText: 'is greater than',
type: FilterType.TextField,
},
{
key: 'accountStatusFilter',
label: 'Account status',
operatorText: 'is',
type: FilterType.Select,
options: ['Enabled', 'Invited', 'Not invited', 'Declined'],
},
]}
appliedFilters={[
{
key: 'orderCountFilter',
value: '1',
label: 'Has orders',
},
{
key: 'accountStatusFilter',
value: 'Enabled',
label: 'Account enabled',
},
]}
onFiltersChange={(appliedFilters) => {
console.log(
`Applied filters changed to ${appliedFilters}.`,
'Todo: use setState to apply this change.',
);
}}
searchValue="USA"
onSearchChange={(searchValue) => {
console.log(
`Search value changed to ${searchValue}.`,
'Todo: use setState to apply this change.',
);
}}
additionalAction={{
content: 'Save',
onAction: () => console.log('Todo: handle save filters.'),
}}
/>
}
/>
```
<a name="subcomponent-filter-control-props"></a>
### Filter control properties
| Prop | Type | Description |
| --------------- | ----------------------------------------------- | ----------------------------------------------- |
| searchValue | string | Currently entered text in the search term field |
| appliedFilters | AppliedFilter[] | Collection of currently applied filters |
| focused | boolean | Whether the search term field is focused |
| filters | Filter[] | Available filters |
| onSearchBlur | function(): void | Callback when the search term field is blurred |
| onSearchChange | function(searchvalue: string, id: string): void | Callback when the search term field is changed |
| onFiltersChange | function(appliedFilters: AppliedFilter[]): void | Callback when the applied filters are changed |
<a name="subcomponent-filter-control-best-practices"></a>
### Filter control best practices
A Resource list’s filter control should:
- Make filters available that make common merchant tasks easy. For example, provide the option for merchants to filter a customer’s list to email subscribers only. Don’t offer arbitrary filters.
- Show relevant results for a wide range of search inputs, including partial words. For example, if merchants type “unful” in the search field for an orders list, it should return all unfulfilled orders as a the result (as well as orders with this string elsewhere in Shopify, such as in an order note).
<a name="subcomponent-filter-control-content-guidelines"></a>
### Filter control content guidelines
Content for the filter control appears in two places: the filter builder and the removable tags that represent applied filters.
#### Filter builder content
The filter builder itself has three parts: the **label**, the **operator text**, and the **filter input**.
<div class="TypeContainerImage TypeContainerImage--PageBackground">

</div>
In this example:
- “Account status” is the **label**
- “is” is the **operator text**
- “Enabled” is one of several options that make up the **filter input**
Here’s another example:
<div class="TypeContainerImage TypeContainerImage--PageBackground">

</div>
In this case, a the **filter input** is a text field, so you only need to consider copy for the **label**, “Number of orders” and **operator text**, “is greater than”.
- Filter label and filter input should follow the [select menu options guidelines](https://polaris.shopify.com/components/forms/select#section-content-guidelines)
- Operator text should start with a lowercase letter
- All three content elements should form a sentence
- Operator text may be left out if the sentence reads more clearly without it
### Applied filter tags
<div class="TypeContainerImage">

</div>
The content that represents applied filter tags should use short, clear, non-technical labels.
<!-- usagelist -->
#### Do
- Has orders
- More than 10 orders
#### Don’t
- Number of orders is greater than 0
- order_count >= 10
<!-- end -->
---
## Related components
- To present structured data for comparison and analysis, like when helping merchants to gain insights or review analytics, use the [data table component](/components/lists-and-tables/data-table)
- To display a simple list of related content, [use the list component](/components/lists-and-tables/list)
---
<a name="study"></a>
## Case study
To cover the resource list component in depth, we’ll create a customer list as an example. We’ll start by implementing a basic resoure list step by step. Then we’ll customize the built-in resource list item to better display our customers. Finally, we’ll add features to make the list more useful to merchants.
1. [Development setup](#study-setup) (optional)
1. [A basic resource list](#study-basic-list)
1. [Building a reusable custom list item](#study-custom-item)
1. [Adding bulk actions](#study-bulk-actions)
1. [Adding sorting](#study-sorting)
1. [Adding filtering](#study-filtering)
1. [Adding pagination](#study-pagination)
You can also [jump straight to the end result](#study-end-result).
<a name="study-setup"></a>
### Development setup (optional)
If you want to follow along with the code, our setup will be based on Create React App. If you’ve never used Create React App, you can get started by using `npx` (npm 5.2+):
```bash
npx create-react-app my-app
cd my-app
npm start
```
Your browser will open to `localhost:3000` and update as you code. You can [learn more about Create React App on GitHub](https://github.com/facebook/create-react-app)
The main files in our project directory look like this:
```
my-app/
README.md
node_modules/
package.json
src/
App.css
App.js
App.test.js
index.css
index.js
```
We’ll open our `App.js` and replace it with this:
```jsx
import React, {Component} from 'react';
class App extends Component {
render() {
return <p>Hello world</p>;
}
}
export default App;
```
You should now see “Hello World” in your browser.
Next, we need to add the React Polaris library to our project. We’ll install it using npm:
```bash
npm install @shopify/polaris --save
```
The last thing before we start building is to import the Polaris styles and the components we’ll need.
```jsx
import React, { Component } from 'react';
import {
Page,
Card,
ResourceList,
TextStyle,
Avatar,
} from '@shopify/polaris';
import '@shopify/polaris/styles.css';
...
```
<a name="study-basic-list"></a>
### A basic resource list
Let’s start with some sample data. In a real app the customer data would come from an API endpoint or as part of the initial payload from the server.
```jsx
...
const customers = [
{
id: 341,
url: 'customers/341',
name: 'Mae Jemison',
location: 'Decatur, USA',
},
{
id: 256,
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
},
];
...
```
Notice that we’ve included an `id` for each customer. This should be a unique identifier from our database.
We also should make sure to sort our items in a way that makes sense to merchants. For this case study, let’s assume we’ve sorted this list by most recent update.
With our sample data in place we can now display a simple resource list:
```jsx
...
const resourceName = {
singular: 'customer',
plural: 'customers',
};
class App extends Component {
render() {
return (
<Page title="Customers">
<Card>
<ResourceList
resourceName={resourceName}
items={customers}
renderItem={(item) => {
const { id, url, name, location } = item;
const media = <Avatar customer size="medium" name={name} />;
return (
<ResourceList.Item id={id} url={url} media={media} accessibilityLabel={`View details for ${name}`}>
<h3><TextStyle variation="strong">{name}</TextStyle></h3>
<div>{location}</div>
</ResourceList.Item>
);
}}
/>
</Card>
</Page>
);
}
}
...
```
Let’s take a closer look this code.
Notice we’re providing a prop for the singular and plural name of our resource (“customers”). The resource list component will use these strings to build various pieces of content in the component, such as “Showing 50 customers”.
We’ve broken out our item renderer as a separate function to keep things clean.
Next, we’ll build is a customized resource list item.
<a name="study-custom-item"></a>
### Building a reusable custom list item
A list of orders is different than a list of products and is used by merchants differently. As a result, most resource lists benefit from careful choice of content and a customized layout. The best way to do this is to customize the built-in [resource list item](#subcomponent-item).
In this section, we’ll build a custom resource list item for customers:
<div class="TypeContainerImage TypeContainerImage--PageBackground">

</div>
<!-- resources -->
#### Resources
##### Get the example code
- https://github.com/Shopify/polaris-resource-list-examples/tree/v0.1.0/src/components/CustomerListItem
- View example code for custom resource list items
- dev
<!-- end -->
<a name="subcomponent-custom-item-content"></a>
#### Defining the content
We’ll start by figuring out what information and actions merchants need when working with customers.
- What content is useful to describe the customer?
- What content do merchants need to find a specific customer?
- What content related to the customer will help merchants fulfill an order or make a sale?
The customer name is essential. Their physical location is helpful too, especially for merchants with retail stores or multiple locations. Since orders and customer loyalty are important, the customer’s total order count and total spent are also useful for customer loyalty purposes. Finally, we’ll include an avatar for demonstration purposes. Since customers may not have avatars, consider leaving this out.
This gives us the following content, ranked roughly by importance:
1. Customer name
1. Location
1. Number of orders
1. Total spent
1. Avatar
##### Crafting the copy
Resource lists don’t have column headings, so care must be taken to avoid ambiguous copy.
1. Start by listing out typical values for each piece of content. If the value alone speaks for itself we can use it as-is.
<!-- usagelist -->
#### Do
- Adam West
- Ottawa, Canada
#### Don’t
- 3
- \$492.76
<!-- end -->
2. If a value alone is ambiguous, like the number of orders and total spent, add text to make it clear. When possible, use a short phrase rather than a label with a colon.
<!-- usagelist -->
#### Do
- 3 orders
#### Don’t
- 3
- Total orders: 3
<!-- end -->
3. If a content value is empty for a given item, use a phrase to describe the empty state. For a customer with no orders, use “No orders”. If the value is numeric, “0” may be used. Don’t indicated empty values with em dash (“—”).
When a core content element is empty, show it grayed out using the subdued [text style](/components/titles-and-text/text-style) variation.
<!-- usagelist -->
#### Do
- No orders
- 0 orders
#### Don’t
- —
<!-- end -->
##### Using badges as content
The [badge component](/components/images-and-icons/badge) calls strong attention to itself. Showing a badge on every item in a list will distract from the rest of the content.
Whenever possible, use badges conditionally, showing them only w