List
A list is a continuous, vertical index of text and images: a headline per row, with an overline, supporting text, and leading and trailing content such as an icon, an avatar or a time. Rows are one, two or three lines tall, and can be selected, one at a time or several. See the M3 list guidelines.
Usage
Each item has an id and a headline. By default a row is a button that selects it, and
choosing another row moves the selection.
import { createList } from 'mtrl';
const list = createList({
ariaLabel: 'Ideas for today',
items: [
{ id: 'walk', headline: 'Morning walk' },
{ id: 'read', headline: 'Read a chapter' },
{ id: 'cook', headline: 'Try a new recipe' },
],
});
document.body.append(list.element);
<m-list aria-label="Ideas for today">
<m-list-item value="walk">Morning walk</m-list-item>
<m-list-item value="read">Read a chapter</m-list-item>
<m-list-item value="cook">Try a new recipe</m-list-item>
</m-list>
import { List, ListItem } from 'mtrl/react';
export function Example() {
return (
<List ariaLabel="Ideas for today">
<ListItem value="walk">Morning walk</ListItem>
<ListItem value="read">Read a chapter</ListItem>
<ListItem value="cook">Try a new recipe</ListItem>
</List>
);
}
<script setup lang="ts">
import { MList, MListItem } from 'mtrl/vue';
</script>
<template>
<MList aria-label="Ideas for today">
<MListItem value="walk">Morning walk</MListItem>
<MListItem value="read">Read a chapter</MListItem>
<MListItem value="cook">Try a new recipe</MListItem>
</MList>
</template>
<script lang="ts">
import { List, ListItem } from 'mtrl/svelte';
</script>
<List ariaLabel="Ideas for today">
<ListItem value="walk">Morning walk</ListItem>
<ListItem value="read">Read a chapter</ListItem>
<ListItem value="cook">Try a new recipe</ListItem>
</List>
import { List, ListItem } from 'mtrl/solid';
export function Example() {
return (
<List ariaLabel="Ideas for today">
<ListItem value="walk">Morning walk</ListItem>
<ListItem value="read">Read a chapter</ListItem>
<ListItem value="cook">Try a new recipe</ListItem>
</List>
);
}
Examples
Anatomy
supportingText and overline make a row two or three lines tall; lines sets it. leading
and trailing are { type, content }: an icon, avatar, image or video leading, and
an icon or text trailing. { kind: 'subheader' } titles a group and { kind: 'divider' }
separates one, inset to line up with the text. trackSelection: false makes the rows
display only.
import { createList } from 'mtrl';
const list = createList({
ariaLabel: 'Places',
trackSelection: false,
items: [
{ kind: 'subheader', headline: 'Places to explore' },
{
id: 'trail',
headline: 'Mountain trail',
supportingText: '5 km loop',
leading: { type: 'icon', content: locationIcon },
trailing: { type: 'text', content: '40 min' },
},
{ kind: 'divider', inset: true },
{
id: 'garden',
headline: 'Botanical garden',
supportingText: 'Open until 6 pm',
leading: { type: 'icon', content: locationIcon },
trailing: { type: 'text', content: '15 min' },
},
],
});
document.body.append(list.element);
<m-list aria-label="Places" selection="none">
<m-list-item kind="subheader">Places to explore</m-list-item>
<m-list-item value="trail" supporting-text="5 km loop" trailing-text="40 min">Mountain trail</m-list-item>
<m-list-item kind="divider" inset></m-list-item>
<m-list-item value="garden" supporting-text="Open until 6 pm" trailing-text="15 min">Botanical garden</m-list-item>
</m-list>
<script type="module">
const list = document.querySelector('m-list');
list.querySelector('m-list-item[value="trail"]').setAttribute('leading-icon', locationIcon);
list.querySelector('m-list-item[value="garden"]').setAttribute('leading-icon', locationIcon);
</script>
import { List, ListItem } from 'mtrl/react';
import { locationIcon } from './app';
export function Example() {
return (
<List ariaLabel="Places" selection="none">
<ListItem kind="subheader">Places to explore</ListItem>
<ListItem value="trail" supportingText="5 km loop" leadingIcon={locationIcon} trailingText="40 min">Mountain trail</ListItem>
<ListItem kind="divider" inset />
<ListItem value="garden" supportingText="Open until 6 pm" leadingIcon={locationIcon} trailingText="15 min">Botanical garden</ListItem>
</List>
);
}
<script setup lang="ts">
import { MList, MListItem } from 'mtrl/vue';
import { locationIcon } from './app';
</script>
<template>
<MList aria-label="Places" selection="none">
<MListItem kind="subheader">Places to explore</MListItem>
<MListItem value="trail" supporting-text="5 km loop" :leading-icon="locationIcon" trailing-text="40 min">Mountain trail</MListItem>
<MListItem kind="divider" inset />
<MListItem value="garden" supporting-text="Open until 6 pm" :leading-icon="locationIcon" trailing-text="15 min">Botanical garden</MListItem>
</MList>
</template>
<script lang="ts">
import { List, ListItem } from 'mtrl/svelte';
import { locationIcon } from './app';
</script>
<List ariaLabel="Places" selection="none">
<ListItem kind="subheader">Places to explore</ListItem>
<ListItem value="trail" supportingText="5 km loop" leadingIcon={locationIcon} trailingText="40 min">Mountain trail</ListItem>
<ListItem kind="divider" inset />
<ListItem value="garden" supportingText="Open until 6 pm" leadingIcon={locationIcon} trailingText="15 min">Botanical garden</ListItem>
</List>
import { List, ListItem } from 'mtrl/solid';
import { locationIcon } from './app';
export function Example() {
return (
<List ariaLabel="Places" selection="none">
<ListItem kind="subheader">Places to explore</ListItem>
<ListItem value="trail" supportingText="5 km loop" leadingIcon={locationIcon} trailingText="40 min">Mountain trail</ListItem>
<ListItem kind="divider" inset />
<ListItem value="garden" supportingText="Open until 6 pm" leadingIcon={locationIcon} trailingText="15 min">Botanical garden</ListItem>
</List>
);
}
Several selected
multiSelect lets rows be selected together, and initialSelection (or an item's
selected) sets the rows that start selected. A disabled row cannot be selected or focused.
import { createList } from 'mtrl';
const list = createList({
ariaLabel: 'Countries',
multiSelect: true,
initialSelection: ['fr', 'jp'],
items: [
{ id: 'fr', headline: 'France' },
{ id: 'de', headline: 'Germany', disabled: true },
{ id: 'jp', headline: 'Japan' },
],
});
document.body.append(list.element);
<m-list aria-label="Countries" selection="multiple">
<m-list-item value="fr" selected>France</m-list-item>
<m-list-item value="de" disabled>Germany</m-list-item>
<m-list-item value="jp" selected>Japan</m-list-item>
</m-list>
import { List, ListItem } from 'mtrl/react';
export function Example() {
return (
<List ariaLabel="Countries" selection="multiple">
<ListItem value="fr" selected>France</ListItem>
<ListItem value="de" disabled>Germany</ListItem>
<ListItem value="jp" selected>Japan</ListItem>
</List>
);
}
<script setup lang="ts">
import { MList, MListItem } from 'mtrl/vue';
</script>
<template>
<MList aria-label="Countries" selection="multiple">
<MListItem value="fr" selected>France</MListItem>
<MListItem value="de" disabled>Germany</MListItem>
<MListItem value="jp" selected>Japan</MListItem>
</MList>
</template>
<script lang="ts">
import { List, ListItem } from 'mtrl/svelte';
</script>
<List ariaLabel="Countries" selection="multiple">
<ListItem value="fr" selected>France</ListItem>
<ListItem value="de" disabled>Germany</ListItem>
<ListItem value="jp" selected>Japan</ListItem>
</List>
import { List, ListItem } from 'mtrl/solid';
export function Example() {
return (
<List ariaLabel="Countries" selection="multiple">
<ListItem value="fr" selected>France</ListItem>
<ListItem value="de" disabled>Germany</ListItem>
<ListItem value="jp" selected>Japan</ListItem>
</List>
);
}
A choice emits select with { item, element, originalEvent } before the selection moves;
preventDefault() leaves it where it was. Choosing the selected row again deselects it. The
web component dispatches activate with { value }, then change with { value, values }
when the selection moved. A trailing control or custom slot holds the app's own control,
which a click on does not select the row. renderItem(item, index) renders a row the app's
way. The list renders every item; for long or remote data, use
vlist.
API
Options
| Option | Type | Default | Description |
|---|---|---|---|
items |
ListItem[] |
[] |
The rows, subheaders and dividers |
renderItem |
(item, index) => HTMLElement |
the anatomy | Renders a row's content |
trackSelection |
boolean |
true |
Rows are buttons that select; false, display only |
multiSelect |
boolean |
false |
Several rows selected at once |
initialSelection |
(string | number)[] |
undefined |
The ids selected at creation |
ariaLabel |
string |
undefined |
The list's accessible name |
animate |
boolean |
false |
Whether scrollToItem() and scrollToIndex() scroll smoothly by default |
class |
string |
undefined |
Additional CSS classes |
prefix |
string |
'mtrl' |
Prefix for CSS class names |
componentName |
string |
'list' |
Component name used in class generation |
Items
| Field | Type | Description |
|---|---|---|
kind |
'item' | 'subheader' | 'divider' |
A row by default |
id |
string | number |
The row's id; its index without one |
headline |
string |
The row's text; text, title or name stand in for it |
overline / supportingText |
string |
The line above and the lines below the headline |
lines |
1 | 2 | 3 |
The row's height; inferred from the text without it |
leading |
ListSlot |
{ type: 'icon' | 'avatar' | 'image' | 'video' | 'text' | 'control' | 'custom', content } |
trailing |
ListSlot |
The same, at the end |
selected / disabled |
boolean |
Selected at creation; not selectable |
inset |
boolean |
A divider lined up with the text |
Methods
| Method | Returns | Description |
|---|---|---|
getSelectedItems() / getSelectedItemIds() |
ListItem[] / string[] |
The selection |
isItemSelected(id) |
boolean |
Whether a row is selected |
selectItem(id) / deselectItem(id) |
ListComponent |
Adds a row to the selection, or removes it |
setSelection(ids) / clearSelection() |
ListComponent |
Replaces or clears the selection |
refresh() |
Promise<ListComponent> |
Renders the items again, from the array the list holds |
getAllItems() / getVisibleItems() |
ListItem[] |
The items, all of them in both cases |
scrollToItem(id, position?, animate?) |
ListComponent |
Scrolls a row into view: 'start', 'center' or 'end' |
scrollToIndex(index, position?, animate?) |
Promise<ListComponent> |
The same, by index |
isLoading() / hasNextPage() |
boolean |
Always false |
on(event, handler) / off(event, handler) |
ListComponent |
Events |
destroy() |
void |
Removes the list and its listeners |
Events
| Event | Payload | Description |
|---|---|---|
select |
{ item, value, element, originalEvent, component, preventDefault, defaultPrevented } |
A row was chosen, before the selection moves |
load |
{ items, loading, hasNext, hasPrev, component } |
The items were rendered, at creation and on refresh() |
scroll |
{ originalEvent, component } |
The list scrolled |
Accessibility
- The list is a
listnamed byariaLabel, each row alistitem, a divider aseparator. - A selectable row holds a button named by the headline and described by the supporting text,
with
aria-pressedfor its selection.Up,Down,HomeandEndmove between the enabled rows;EnterorSpaceselects. - A disabled row is
aria-disabledand its button disabled. Leading icons are hidden from assistive tech; a trailing control is the app's to name.
Styling
A selected row has the secondary-container color and 16dp corners.
.mtrl-list { }
.mtrl-list__content, .mtrl-list__subheader, .mtrl-list__divider, .mtrl-list__divider--inset, .mtrl-list__empty { }
.mtrl-list__item, .mtrl-list__item--two-line, .mtrl-list__item--three-line { }
.mtrl-list__item--selected, .mtrl-list__item--disabled, .mtrl-list__action { }
.mtrl-list__text, .mtrl-list__overline, .mtrl-list__headline, .mtrl-list__supporting { }
.mtrl-list__leading, .mtrl-list__leading--icon, .mtrl-list__leading--avatar, .mtrl-list__leading--image, .mtrl-list__leading--video { }
.mtrl-list__trailing, .mtrl-list__trailing--icon, .mtrl-list__trailing--text { }
Measurements
From Compose's ListItem and ListTokens:
| Attribute | Value |
|---|---|
| Row | 56dp one line, 72dp two, 88dp three; 16dp between its parts |
| Padding | 8dp top and bottom (12dp for three lines), 16dp at the sides |
| Text | Headline Body Large on-surface; overline Label Small and supporting text Body Medium, on-surface-variant |
| Leading | Icon 24dp; avatar 40dp; image 56dp; video 100 × 56dp (114 × 64dp in three lines) |
| Trailing text | Label Small |
| Subheader | Label Large, on-surface-variant |
| Divider | 1dp outline-variant; inset 72dp from the start and 16dp from the end |
| Focus | A 2dp secondary ring inside the row |