The Essential Guide to the Material-UI Progress Bar: Color, Percent, and More

The MUI Progress Bar, more formally known as the MUI LinearProgress component, gives a user visual feedback while waiting for resources to load in a web page. In this tutorial I will customize the progress bar with dynamic color based on load value, a percent label, and both vertical and horizontal orientation.

Here’s a screenshot of the linear progress components we will create:

Custom MUI Progress Bar Color and Orientation
Custom MUI Progress Bar Color and Orientation

The Resources section contains full working code and a link to a live Code Sandbox demo.

Change MUI Progress Bar Color

We’ll use useEffect and useState to dynamically update the color of the LinearProgress component based on it’s value prop. We’ll use a timer inside useEffect to set the state value progress (just like this MUI docs tutorial), but then we’ll also set a color state value for updating the progress bar’s sx prop with a color value.

React.useEffect(() => {
    if (progress <= 25) {
      setColor("255,0,0");
    } else if (progress <= 50) {
      setColor("255,255,0");
    } else if (progress <= 75) {
      setColor("0,0,255");
    } else {
      setColor("0,255,0");
    }

    const timer = setInterval(() => {
      setProgress((previousProgress) => {
        if (previousProgress === 100) {
          return 0;
        }
        return previousProgress + 5;
      });
    return () => {
      clearInterval(timer);
    };
  }, [progress]);

//JSX
<LinearProgress
  variant="determinate"
  value={progress}
  sx={{
    backgroundColor: `rgb(${color},0.4)`,
    "& .MuiLinearProgress-bar": {
      backgroundColor: `rgb(${color})`
    }
  }}
/>

Notice in the JSX I used sx instead of the built-in progress bar color prop. The limitation of the color prop is that it can only accept colors directly from the theme palette.

Because of this limitation, I chose to directly set the background color for the progress bar so I had unlimited color options. However, this meant I also had to set the background color for the child span with class MuiLinearProgress-bar. The child element actually contains the darker color bar, while the parent is the full-width and more translucent bar.

The above code is specifically for the determinate variant. The buffer variant also accepts a value prop, but then renders with two child spans so you need a different selector to customize the color.

Add Percent to MUI Progress Bar

I added a FormLabel with a percentage value above the progress bar. This label obtains it’s value from the progress state value that is regularly being updated through a timer in a useEffect hook.

//state value
const [progress, setProgress] = React.useState(0);

//JSX
<FormLabel>{progress}%</FormLabel>

Create a Vertical MUI Progress Bar

The Material-UI Linear Progress component can be oriented vertically through CSS customization of the transform property. I will discuss two different methods to create a vertical progress bar below.

Invert Width, Height, and translateY

The first method expands the height, shrinks the width, and translates the y value of the child span (the darker line).

First, we calculate a state variable that counts down from 100%, then we create the JSX and nested selector:

//state value
const [invertedProgress, setInvertedProgress] = React.useState(100);

//inside useEffect hook
setInvertedProgress((previousInvertedProgress) => {
  if (previousInvertedProgress === 0) {
    return 100;
  }
  return previousInvertedProgress - 5;
});

//JSX
<LinearProgress

  sx={{
    width: 4,
    height: 200,
    "& span.MuiLinearProgress-bar": {
      transform: `translateY(-${invertedProgress}%) !important` //has to have !important
    }
  }}
  value={invertedProgress}
/>

The JSX is the tricky part. My progress bar “moves” from top to bottom, so that means I need to count down from 100. Also, I need a negative value for the transform: translateY.

Notice also that I used !important. I never have to do that unless MUI is “behind the scenes” using inline styling instead of classes. Sure enough, look at the DOM screenshot below. I had to override that transform: translateX value in the style attribute with my translateY value.

MUI Progress Bar DOM
MUI Progress Bar DOM

Rotate Progress Bar 90 Degrees

Perhaps the easier method conceptually is rotating the bar 90 degrees with transform: rotate(90deg). However, it’s major drawback is that it pivots around its central point and requires some micromanagement to place it properly.

<LinearProgress
  variant="determinate"
  sx={{
    transform: "rotate(90deg)",
    width: 200,
    position: "fixed",
    top: 188,
    left: -50
  }}
  value={progress}
/>

In this case I used position: fixed, top, and left to position the linear progress bar.

Resources

Full Code:

import * as React from "react";
import Stack from "@mui/material/Stack";
import FormLabel from "@mui/material/FormLabel";
import LinearProgress from "@mui/material/LinearProgress";

export default function LinearDeterminate() {
  const [progress, setProgress] = React.useState(0);
  const [invertedProgress, setInvertedProgress] = React.useState(100);
  const [color, setColor] = React.useState("red");

  React.useEffect(() => {
    if (progress <= 25) {
      setColor("255,0,0");
    } else if (progress <= 50) {
      setColor("255,255,0");
    } else if (progress <= 75) {
      setColor("0,0,255");
    } else {
      setColor("0,255,0");
    }

    const timer = setInterval(() => {
      setProgress((previousProgress) => {
        if (previousProgress === 100) {
          return 0;
        }
        return previousProgress + 5;
      });
      setInvertedProgress((previousInvertedProgress) => {
        if (previousInvertedProgress === 0) {
          return 100;
        }
        return previousInvertedProgress - 5;
      });
    }, 750);

    return () => {
      clearInterval(timer);
    };
  }, [progress]);

  return (
    <Stack sx={{ width: "100%" }}>
      <FormLabel>{progress}%</FormLabel>
      <LinearProgress
        variant="determinate"
        value={progress}
        sx={{
          backgroundColor: `rgb(${color},0.4)`,
          "& .MuiLinearProgress-bar": {
            backgroundColor: `rgb(${color})`
          }
        }}
      />
      <FormLabel sx={{ marginTop: 4 }}>Vertical Progress</FormLabel>
      <LinearProgress
        sx={{
          width: 4,
          height: 200,
          "& span.MuiLinearProgress-bar": {
            transform: `translateY(-${invertedProgress}%) !important` //has to have !important
          }
        }}
        variant="determinate"
        value={invertedProgress}
      />
      <LinearProgress
        sx={{
          transform: "rotate(90deg)",
          width: 200,
          position: "fixed",
          top: 190,
          left: -50
        }}
        variant="determinate"
        value={progress}
      />
    </Stack>
  );
}

In this example I oriented the MUI chip vertically.

The MUI Skeleton Loader is another choice for visual feedback while resources are loading.

Code Sandbox Link

MUI Linear Progress Docs

Share this post:

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.