Progress
Progress indicators inform users about the status of ongoing processes, such as loading an app, submitting a form, or saving updates. They communicate an app’s state and indicate available actions, such as whether users can navigate away from the current screen.
All progressbar components require an aria-label
or aria-labelledby
for accessibility. The label will probably be "Loading"
for most use cases,
but could also be things like:
- An
id
of aDialog
title - An
id
of aButton
- An
id
of a heading element - etc
Circular Progress
Circular progress indicators display progress by animating an indicator along an invisible circular track in a clockwise direction. They can be applied directly to a surface, such as a button or card.
The default behavior for a circular progress bar is to be centered within the container element and spin indefinitely.
import { CircularProgress } from "@react-md/core/progress/CircularProgress";
import { type ReactElement } from "react";
export default function SimpleCircularProgress(): ReactElement {
return <CircularProgress aria-label="Example" />;
}
Determinate Circular Progress
If the progress state is quantifiable in a range from 0% - 100%, provide that
value to the CircularProgress
component to create a determinate progress bar.
The progress bar will grow as the value expands to show the current state to the
user.
import { CircularProgress } from "@react-md/core/progress/CircularProgress";
import { loop } from "@react-md/core/utils/loop";
import { type ReactElement, useEffect, useState } from "react";
export default function DeterminateCircularProgress(): ReactElement {
const progress = useProgress();
return (
<>
<CircularProgress aria-label="Example" value={10} />
<CircularProgress aria-label="Example" value={30} />
<CircularProgress aria-label="Example" value={70} />
<CircularProgress aria-label="Example" value={progress} />
</>
);
}
function useProgress(): number {
const [progress, setProgress] = useState(0);
useEffect(() => {
const interval = window.setInterval(() => {
setProgress(
(prev) =>
loop({
min: 0,
max: 10,
value: prev / 10,
increment: true,
}) * 10,
);
}, 1000);
return () => {
window.clearInterval(interval);
};
}, []);
return progress;
}
Circular Progress Sizes
The CircularProgress
also supports a dense
size:
import { CircularProgress } from "@react-md/core/progress/CircularProgress";
import { type ReactElement } from "react";
export default function CircularProgressSizes(): ReactElement {
return <CircularProgress aria-label="Example" dense />;
}
Circular Progress Theme
The CircularProgress
bar supports all the different theme colors and the current text color.
import { Box } from "@react-md/core/box/Box";
import { Switch } from "@react-md/core/form/Switch";
import { CircularProgress } from "@react-md/core/progress/CircularProgress";
import { type ReactElement, useState } from "react";
export default function CircularProgressTheme(): ReactElement {
const [checked, setChecked] = useState(false);
const value = checked ? undefined : 30;
return (
<>
<CircularProgress aria-label="Example" value={value} theme="primary" />
<CircularProgress aria-label="Example" value={value} theme="secondary" />
<CircularProgress aria-label="Example" value={value} theme="warning" />
<CircularProgress aria-label="Example" value={value} theme="success" />
<CircularProgress aria-label="Example" value={value} theme="error" />
<CircularProgress
aria-label="Example"
value={checked ? undefined : 30}
theme="current-color"
/>
<Box disablePadding fullWidth>
<Switch
label="Run"
checked={checked}
onChange={(event) => {
setChecked(event.currentTarget.checked);
}}
/>
</Box>
</>
);
}
Performance Concerns
When running CPU intensive tasks, the CircularProgress
animation might appear
sluggish because it animates using a rotate and a scaling stroke-dashoffset. It
is recommended to move CPU intensive tasks to a
Web Worker
when possible, but the animation can be simplified to only a rotation by
enabling the disableShrink
prop.
import { CircularProgress } from "@react-md/core/progress/CircularProgress";
import { type ReactElement } from "react";
export default function PerformanceConcerns(): ReactElement {
return <CircularProgress aria-label="Example" disableShrink />;
}
Linear Progress
Linear progress indicators support both determinate and indeterminate operations.
- Determinate operations display the indicator increasing in width from 0 to 100% of the track, in sync with the process’s progress.
- Indeterminate operations display the indicator continually growing and shrinking along the track until the process is complete.
import { LinearProgress } from "@react-md/core/progress/LinearProgress";
import { type ReactElement } from "react";
export default function SimpleLinearProgress(): ReactElement {
return <LinearProgress aria-label="Example" />;
}
Linear Progress Theme
The LinearProgress
bar kind of supports all the different theme colors and
the current text color. The colors will probably need to be modified to be more
visible based on the background color.
import { Box } from "@react-md/core/box/Box";
import { Switch } from "@react-md/core/form/Switch";
import { LinearProgress } from "@react-md/core/progress/LinearProgress";
import { type ReactElement, useState } from "react";
export default function LinearProgressTheme(): ReactElement {
const [checked, setChecked] = useState(false);
const value = checked ? undefined : 30;
return (
<>
<LinearProgress aria-label="Example" value={value} theme="primary" />
<LinearProgress aria-label="Example" value={value} theme="secondary" />
<LinearProgress aria-label="Example" value={value} theme="warning" />
<LinearProgress aria-label="Example" value={value} theme="success" />
<LinearProgress aria-label="Example" value={value} theme="error" />
<LinearProgress
aria-label="Example"
value={checked ? undefined : 30}
theme="current-color"
/>
<Box disablePadding fullWidth>
<Switch
label="Run"
checked={checked}
onChange={(event) => {
setChecked(event.currentTarget.checked);
}}
/>
</Box>
</>
);
}
Disable Determinate Transition
The determinate circular and linear progress bars animate to the next value after 200ms
which might cause a delay if the value updates more quickly than that. In those cases,
enable the disableTransition
prop.
"use client";
import { Box } from "@react-md/core/box/Box";
import { Button } from "@react-md/core/button/Button";
import { Switch } from "@react-md/core/form/Switch";
import { CircularProgress } from "@react-md/core/progress/CircularProgress";
import { LinearProgress } from "@react-md/core/progress/LinearProgress";
import { type ReactElement, useEffect, useState } from "react";
export default function DisableDeterminateTransition(): ReactElement {
const { progress, toggle, restart, running } = useProgress();
return (
<Box stacked align="start" fullWidth>
<CircularProgress
aria-label="Example"
value={progress}
disableTransition
/>
<LinearProgress aria-label="Example" value={progress} disableTransition />
<Switch label="Run" checked={running} onChange={toggle} />
<Button onClick={restart}>Restart</Button>
</Box>
);
}
const UPDATE_INTERVAL = 10;
interface ProgressControls {
toggle: () => void;
restart: () => void;
running: boolean;
progress: number;
}
function useProgress(): ProgressControls {
const [state, setState] = useState({
running: false,
progress: 0,
});
const { running, progress } = state;
useEffect(() => {
if (!running) {
return;
}
const timeout = window.setTimeout(() => {
const nextProgress = Math.min(100, progress + 0.1);
setState({
running: progress !== nextProgress && progress !== 100,
progress: nextProgress,
});
}, UPDATE_INTERVAL);
return () => {
window.clearTimeout(timeout);
};
}, [progress, running]);
return {
toggle: () => {
setState((prev) => ({ ...prev, running: !prev.running }));
},
restart: () => {
setState({ running: false, progress: 0 });
},
running,
progress,
};
}
Within Buttons
Check out the Async Button to see how circular and linear progress bars can be rendered within buttons.