/Documentation

List

PublishedUpdated

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.

Vanilla
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);
Web Components
<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>
React
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>
  );
}
Vue
<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>
Svelte
<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>
SolidJS
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.

Vanilla
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);
Web Components
<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>
React
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>
  );
}
Vue
<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>
Svelte
<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>
SolidJS
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.

Vanilla
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);
Web Components
<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>
React
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>
  );
}
Vue
<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>
Svelte
<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>
SolidJS
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 list named by ariaLabel, each row a listitem, a divider a separator.
  • A selectable row holds a button named by the headline and described by the supporting text, with aria-pressed for its selection. Up, Down, Home and End move between the enabled rows; Enter or Space selects.
  • A disabled row is aria-disabled and 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