Loading indicator
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.
import { createLoadingIndicator } from 'mtrl';
const indicator = createLoadingIndicator({ ariaLabel: 'Loading articles' });
document.body.append(indicator.element);
<m-loading-indicator aria-label="Loading articles"></m-loading-indicator>
import { LoadingIndicator } from 'mtrl/react';
export function Example() {
return (
<LoadingIndicator ariaLabel="Loading articles" />
);
}
<script setup lang="ts">
import { MLoadingIndicator } from 'mtrl/vue';
</script>
<template>
<MLoadingIndicator aria-label="Loading articles" />
</template>
<script lang="ts">
import { LoadingIndicator } from 'mtrl/svelte';
</script>
<LoadingIndicator ariaLabel="Loading articles" />
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.
import { createLoadingIndicator } from 'mtrl';
const indicator = createLoadingIndicator({ contained: true, ariaLabel: 'Loading map' });
document.body.append(indicator.element);
<m-loading-indicator contained aria-label="Loading map"></m-loading-indicator>
import { LoadingIndicator } from 'mtrl/react';
export function Example() {
return (
<LoadingIndicator contained ariaLabel="Loading map" />
);
}
<script setup lang="ts">
import { MLoadingIndicator } from 'mtrl/vue';
</script>
<template>
<MLoadingIndicator contained aria-label="Loading map" />
</template>
<script lang="ts">
import { LoadingIndicator } from 'mtrl/svelte';
</script>
<LoadingIndicator contained ariaLabel="Loading map" />
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.
import { createLoadingIndicator } from 'mtrl';
const indicator = createLoadingIndicator({ size: 96, ariaLabel: 'Loading album' });
document.body.append(indicator.element);
<m-loading-indicator size="96" aria-label="Loading album"></m-loading-indicator>
import { LoadingIndicator } from 'mtrl/react';
export function Example() {
return (
<LoadingIndicator size={96} ariaLabel="Loading album" />
);
}
<script setup lang="ts">
import { MLoadingIndicator } from 'mtrl/vue';
</script>
<template>
<MLoadingIndicator :size="96" aria-label="Loading album" />
</template>
<script lang="ts">
import { LoadingIndicator } from 'mtrl/svelte';
</script>
<LoadingIndicator size={96} ariaLabel="Loading album" />
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.
import { createLoadingIndicator } from 'mtrl';
const indicator = createLoadingIndicator({ value: 0.2, ariaLabel: 'Uploading photo' });
document.body.append(indicator.element);
function advance() {
indicator.setValue(0.6);
}
<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>
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" />
);
}
<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>
<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" />
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 byariaLabel: say what is loading. The canvas is hidden. - Determinate, it has
aria-valuemin,aria-valuemaxandaria-valuenowfrom 0 to 100; looping, none of them. - Under
prefers-reduced-motionit 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 |