Progress
A progress indicator shows how far a process has come, such as an upload, or that one is running when its length is unknown. It is linear or circular, determinate or indeterminate, and its active indicator is flat or wavy. See the M3 progress indicators guidelines.
Usage
ariaLabel says what is in progress.
import { createProgress } from 'mtrl';
const progress = createProgress({ value: 40, ariaLabel: 'Uploading photo' });
document.body.append(progress.element);
<m-progress value="40" aria-label="Uploading photo"></m-progress>
import { Progress } from 'mtrl/react';
export function Example() {
return (
<Progress value={40} ariaLabel="Uploading photo" />
);
}
<script setup lang="ts">
import { MProgress } from 'mtrl/vue';
</script>
<template>
<MProgress :value="40" aria-label="Uploading photo" />
</template>
<script lang="ts">
import { Progress } from 'mtrl/svelte';
</script>
<Progress value={40} ariaLabel="Uploading photo" />
import { Progress } from 'mtrl/solid';
export function Example() {
return (
<Progress value={40} ariaLabel="Uploading photo" />
);
}
Examples
Circular, indeterminate
indeterminate shows activity without a value. A circular indicator is 40px across, 48px when
wavy; size sets it, from 24 to 240.
import { createProgress } from 'mtrl';
const progress = createProgress({
variant: 'circular',
indeterminate: true,
ariaLabel: 'Loading messages',
});
document.body.append(progress.element);
<m-progress variant="circular" indeterminate aria-label="Loading messages"></m-progress>
import { Progress } from 'mtrl/react';
export function Example() {
return (
<Progress variant="circular" indeterminate ariaLabel="Loading messages" />
);
}
<script setup lang="ts">
import { MProgress } from 'mtrl/vue';
</script>
<template>
<MProgress variant="circular" indeterminate aria-label="Loading messages" />
</template>
<script lang="ts">
import { Progress } from 'mtrl/svelte';
</script>
<Progress variant="circular" indeterminate ariaLabel="Loading messages" />
import { Progress } from 'mtrl/solid';
export function Example() {
return (
<Progress variant="circular" indeterminate ariaLabel="Loading messages" />
);
}
Wavy and thick
shape: 'wavy' draws the active indicator as a wave, which flattens near the start and the
end. thickness is thin (4px), thick (8px) or a number of pixels.
import { createProgress } from 'mtrl';
const progress = createProgress({
shape: 'wavy',
thickness: 'thick',
value: 60,
ariaLabel: 'Installing update',
});
document.body.append(progress.element);
<m-progress value="60" shape="wavy" thickness="thick" aria-label="Installing update"></m-progress>
import { Progress } from 'mtrl/react';
export function Example() {
return (
<Progress value={60} shape="wavy" thickness="thick" ariaLabel="Installing update" />
);
}
<script setup lang="ts">
import { MProgress } from 'mtrl/vue';
</script>
<template>
<MProgress :value="60" shape="wavy" thickness="thick" aria-label="Installing update" />
</template>
<script lang="ts">
import { Progress } from 'mtrl/svelte';
</script>
<Progress value={60} shape="wavy" thickness="thick" ariaLabel="Installing update" />
import { Progress } from 'mtrl/solid';
export function Example() {
return (
<Progress value={60} shape="wavy" thickness="thick" ariaLabel="Installing update" />
);
}
Setting the value
An action sets the value, which animates to it over 500ms; setValue(value, false) jumps. A
value out of range is clamped to 0 or max, and that is the value drawn, announced,
labelled and emitted.
import { createProgress } from 'mtrl';
const progress = createProgress({ value: 30, buffer: 60, ariaLabel: 'Streaming video' });
document.body.append(progress.element);
function advance() {
progress.setValue(75);
}
<m-progress value="30" buffer="60" aria-label="Streaming video"></m-progress>
<script type="module">
const progress = document.querySelector('m-progress');
function advance() {
progress.value = 75;
}
</script>
import { useState } from 'react';
import { Progress } from 'mtrl/react';
export function Example() {
const [value, setValue] = useState(30);
const advance = () => setValue(75);
return (
<Progress value={value} buffer={60} ariaLabel="Streaming video" />
);
}
<script setup lang="ts">
import { ref } from 'vue';
import { MProgress } from 'mtrl/vue';
const value = ref(30);
function advance() {
value.value = 75;
}
</script>
<template>
<MProgress :value="value" :buffer="60" aria-label="Streaming video" />
</template>
<script lang="ts">
import { Progress } from 'mtrl/svelte';
let value = $state(30);
function advance() {
value = 75;
}
</script>
<Progress value={value} buffer={60} ariaLabel="Streaming video" />
import { createSignal } from 'solid-js';
import { Progress } from 'mtrl/solid';
export function Example() {
const [value, setValue] = createSignal(30);
const advance = () => setValue(75);
return (
<Progress value={value()} buffer={60} ariaLabel="Streaming video" />
);
}
buffer draws how much is ready ahead of a linear value, as for streaming media. showLabel
shows the percentage beside the indicator, and labelFormatter writes it differently. Recipes
such as an upload with progress are planned for Examples.
API
Options
| Option | Type | Default | Description |
|---|---|---|---|
variant |
'linear' | 'circular' |
'linear' |
The form of the indicator |
value |
number |
0 |
The value, from 0 to max |
max |
number |
100 |
The value at completion |
buffer |
number |
0 |
The buffered value of a linear indicator |
indeterminate |
boolean |
false |
Activity without a value |
shape |
'flat' | 'wavy' |
'flat' |
A flat or a wavy active indicator |
thickness |
'thin' | 'thick' | number |
'thin' |
4px, 8px, or a number of pixels |
size |
number |
40, 48 when wavy |
The diameter of a circular indicator, from 24 to 240 |
showStopIndicator |
boolean |
true |
The dot at the end of a determinate linear track |
showLabel |
boolean |
false |
Shows the value as a label |
labelFormatter |
(value, max) => string |
a percentage | Writes the label |
ariaLabel |
string |
'Loading' |
What is in progress |
disabled |
boolean |
false |
Whether it starts disabled |
class |
string |
undefined |
Additional CSS classes |
prefix |
string |
'mtrl' |
Prefix for CSS class names |
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
getValue() / setValue(value, animate?) |
value: number, animate?: boolean |
number / ProgressComponent |
The value; animate is true by default |
getMax() |
none | number |
The maximum |
getBuffer() / setBuffer(value) |
value: number |
number / ProgressComponent |
The buffered value |
isIndeterminate() / setIndeterminate(indeterminate) |
indeterminate: boolean |
boolean / ProgressComponent |
Whether it is indeterminate |
getShape() / setShape(shape) |
shape: 'flat' | 'wavy' |
string / ProgressComponent |
The shape |
getThickness() / setThickness(thickness) |
thickness: 'thin' | 'thick' | number |
number / ProgressComponent |
The thickness, read in pixels |
getSize() / setSize(size) |
size: number |
number | undefined / ProgressComponent |
A circular indicator's diameter |
showLabel() / hideLabel() |
none | ProgressComponent |
The label |
setLabelFormatter(formatter) |
formatter: (value, max) => string |
ProgressComponent |
How the label is written |
show() / hide() / isVisible() |
none | ProgressComponent / boolean |
Shows or hides it; hidden, it stops animating |
enable() / disable() / isDisabled() |
none | ProgressComponent / boolean |
The disabled state, with aria-disabled, set the same way at creation |
painted() |
none | Promise<void> |
Resolves once it has been drawn |
on(event, handler) / off(event, handler) |
event: 'change' | 'complete', handler: Function |
ProgressComponent |
Adds or removes a listener |
addClass(...classes) |
...classes: string[] |
ProgressComponent |
Adds CSS classes |
destroy() |
none | void |
Stops its animations and removes it |
Events
| Event | Description | Data |
|---|---|---|
change |
The value was set | { value, max } |
complete |
The value reached max: after the animation, or at once without one |
{ value, max } |
The web component dispatches no events: its value and indeterminate properties are the
live state, and its attributes the first one.
Accessibility
- A
progressbarwitharia-valuemin,aria-valuemaxandaria-valuenow; indeterminate, it has noaria-valuenow. The canvas it draws on is hidden. ariaLabelnames it,'Loading'without one: say what is loading, such as "Loading news article".- The stop indicator is needed unless the track has a 3:1 contrast with what is around it.
- A linear indicator is mirrored in a right-to-left layout. Under
prefers-reduced-motionnothing moves: it draws a still frame.
Styling
The indicator is drawn on a canvas, in the theme's colors: the active and stop indicators in
--mtrl-sys-color-primary, the track in --mtrl-sys-color-secondary-container. They are read
again when the theme changes.
.mtrl-progress, .mtrl-progress--linear, .mtrl-progress--circular { }
.mtrl-progress--indeterminate, .mtrl-progress--disabled { }
.mtrl-progress__canvas, .mtrl-progress__label { }
Measurements
| Attribute | Value |
|---|---|
| Track | 4dp thin, 8dp thick; secondary-container |
| Active indicator | primary, round caps |
| Gap between the indicator and the track | 4dp |
| Stop indicator | 4dp, primary |
| Circular | 40dp, 48dp when wavy |
| Wave | 3dp amplitude and 40dp wavelength on a linear indicator (20dp indeterminate); 1.6dp and 15dp on a 40dp circle, scaled with its size |
| Value change | 500ms, linear |
| Label | Label Medium, on-surface-variant |