/Documentation

Loading indicator

PublishedUpdated

A loading indicator shows a short wait, between 200ms and 5 seconds, for content that is loading or refreshing. Its shape morphs through seven Material shapes as it turns. For longer waits, or work whose progress is worth watching, use a progress indicator. See the M3 loading indicator guidelines.

Usage

It animates from the start. ariaLabel says what is loading.

Vanilla
import { createLoadingIndicator } from 'mtrl';

const indicator = createLoadingIndicator({ ariaLabel: 'Loading articles' });
document.body.append(indicator.element);
Web Components
<m-loading-indicator aria-label="Loading articles"></m-loading-indicator>
React
import { LoadingIndicator } from 'mtrl/react';

export function Example() {
  return (
    <LoadingIndicator ariaLabel="Loading articles" />
  );
}
Vue
<script setup lang="ts">
import { MLoadingIndicator } from 'mtrl/vue';
</script>

<template>
  <MLoadingIndicator aria-label="Loading articles" />
</template>
Svelte
<script lang="ts">
  import { LoadingIndicator } from 'mtrl/svelte';
</script>

<LoadingIndicator ariaLabel="Loading articles" />
SolidJS
import { LoadingIndicator } from 'mtrl/solid';

export function Example() {
  return (
    <LoadingIndicator ariaLabel="Loading articles" />
  );
}

Examples

Contained

Over content such as an image or a map, contained puts it on a primary-container circle for contrast.

Vanilla
import { createLoadingIndicator } from 'mtrl';

const indicator = createLoadingIndicator({ contained: true, ariaLabel: 'Loading map' });
document.body.append(indicator.element);
Web Components
<m-loading-indicator contained aria-label="Loading map"></m-loading-indicator>
React
import { LoadingIndicator } from 'mtrl/react';

export function Example() {
  return (
    <LoadingIndicator contained ariaLabel="Loading map" />
  );
}
Vue
<script setup lang="ts">
import { MLoadingIndicator } from 'mtrl/vue';
</script>

<template>
  <MLoadingIndicator contained aria-label="Loading map" />
</template>
Svelte
<script lang="ts">
  import { LoadingIndicator } from 'mtrl/svelte';
</script>

<LoadingIndicator contained ariaLabel="Loading map" />
SolidJS
import { LoadingIndicator } from 'mtrl/solid';

export function Example() {
  return (
    <LoadingIndicator contained ariaLabel="Loading map" />
  );
}

Size

48px by default, from 24 to 240; the shape keeps its proportions.

Vanilla
import { createLoadingIndicator } from 'mtrl';

const indicator = createLoadingIndicator({ size: 96, ariaLabel: 'Loading album' });
document.body.append(indicator.element);
Web Components
<m-loading-indicator size="96" aria-label="Loading album"></m-loading-indicator>
React
import { LoadingIndicator } from 'mtrl/react';

export function Example() {
  return (
    <LoadingIndicator size={96} ariaLabel="Loading album" />
  );
}
Vue
<script setup lang="ts">
import { MLoadingIndicator } from 'mtrl/vue';
</script>

<template>
  <MLoadingIndicator :size="96" aria-label="Loading album" />
</template>
Svelte
<script lang="ts">
  import { LoadingIndicator } from 'mtrl/svelte';
</script>

<LoadingIndicator size={96} ariaLabel="Loading album" />
SolidJS
import { LoadingIndicator } from 'mtrl/solid';

export function Example() {
  return (
    <LoadingIndicator size={96} ariaLabel="Loading album" />
  );
}

Determinate

A value from 0 to 1 makes it determinate: the shape morphs from a circle to a soft burst, and turns, as the value grows. It follows the value you set, without animating between them; null makes it loop again.

Vanilla
import { createLoadingIndicator } from 'mtrl';

const indicator = createLoadingIndicator({ value: 0.2, ariaLabel: 'Uploading photo' });
document.body.append(indicator.element);

function advance() {
  indicator.setValue(0.6);
}
Web Components
<m-loading-indicator value="0.2" aria-label="Uploading photo"></m-loading-indicator>

<script type="module">
  const loadingIndicator = document.querySelector('m-loading-indicator');

  function advance() {
    loadingIndicator.setAttribute('value', "0.6");
  }
</script>
React
import { useState } from 'react';
import { LoadingIndicator } from 'mtrl/react';

export function Example() {
  const [value, setValue] = useState(0.2);
  const advance = () => setValue(0.6);

  return (
    <LoadingIndicator value={value} ariaLabel="Uploading photo" />
  );
}
Vue
<script setup lang="ts">
import { ref } from 'vue';
import { MLoadingIndicator } from 'mtrl/vue';

const value = ref(0.2);

function advance() {
  value.value = 0.6;
}
</script>

<template>
  <MLoadingIndicator :value="value" aria-label="Uploading photo" />
</template>
Svelte
<script lang="ts">
  import { LoadingIndicator } from 'mtrl/svelte';

  let value = $state(0.2);

  function advance() {
    value = 0.6;
  }
</script>

<LoadingIndicator value={value} ariaLabel="Uploading photo" />
SolidJS
import { createSignal } from 'solid-js';
import { LoadingIndicator } from 'mtrl/solid';

export function Example() {
  const [value, setValue] = createSignal(0.2);
  const advance = () => setValue(0.6);

  return (
    <LoadingIndicator value={value()} ariaLabel="Uploading photo" />
  );
}

stop() freezes it on its current frame, and start() runs it again. Recipes such as a button that shows it while its action runs are planned for Examples.

API

Options

Option Type Default Description
size number 48 Its width and height in pixels, from 24 to 240
contained boolean false On a primary-container circle
value number | null null From 0 to 1, determinate; null loops
ariaLabel string 'Loading' What is loading
class string undefined Additional CSS classes
prefix string 'mtrl' Prefix for CSS class names

Methods

Method Parameters Returns Description
getValue() / setValue(value) value: number | null number | null / LoadingIndicatorComponent The value, from 0 to 1, or null
getSize() / setSize(size) size: number number / LoadingIndicatorComponent The size in pixels, from 24 to 240
setLabel(label) label: string LoadingIndicatorComponent The accessible name
start() / stop() none LoadingIndicatorComponent Runs or freezes the animation
isRunning() none boolean Whether it is running
destroy() none void Stops it and removes it
Property Type Description
element HTMLElement The root, a progressbar
canvas HTMLCanvasElement The canvas it draws on

A loading indicator has no events. The web component's attributes are size, contained, value and aria-label, and start() and stop() are its methods.

Accessibility

  • A progressbar, named by ariaLabel: say what is loading. The canvas is hidden.
  • Determinate, it has aria-valuemin, aria-valuemax and aria-valuenow from 0 to 100; looping, none of them.
  • Under prefers-reduced-motion it shows the shapes in turn, without morphing or turning.
  • It is not focusable; don't move focus to it.

Styling

The shape is drawn in the element's color, so CSS can recolor the factory's indicator, for instance inside a filled button:

.mtrl-loading-indicator, .mtrl-loading-indicator--contained, .mtrl-loading-indicator--determinate { }
.mtrl-loading-indicator__canvas { }

.mtrl-button--filled .mtrl-loading-indicator {
  color: var(--mtrl-sys-color-on-primary);
}

Measurements

Attribute Value
Container 48dp
Active indicator 38dp, primary
Contained A primary-container circle, the indicator on-primary-container
Size 24dp to 240dp, in proportion
Motion A morph to the next shape every 650ms, on a spring (damping 0.6, stiffness 200), with a quarter turn; a full turn every 4666ms besides