DEV Community

Cover image for TIL - HTML Progress Bar
James Cox
James Cox

Posted on

TIL - HTML Progress Bar

#TIL

Today I learned HTML has a native progress bar.

The HTML <progress> element displays an indicator showing the completion progress of a task, typically displayed as a progress bar.

I like this element because you can do a lot of things with it.

  • Want a visual display for some sort of count-up or count-down experience on your site? Wire it up to a progress element.
  • Want a native loader? HTML's progress bar has you covered!
  • Want to show a user how many remaining characters are available in an input or textfield? You guessed it!
  • I'm sure there are plenty of use-cases, so if you can think of others, plop them down in the comments.

The Demo

I demonstrated what I thought is the easiest: using a progress bar to represent the amount of characters remaining in an input field. Check it out:

Start typing away in the textfield and watch the progress bar increase or decrease depending on how many characters you add or delete. Pretty cool huh?

The Code

import "./styles.css";
import { useState } from "react";

export default function App() {
  const [chars, setChars] = useState(0);
  const [text, setText] = useState("");

  const handleCharsAndText = (e) => {
    setChars(e.target.value.length);
    setText(e.target.value);
  };

  const clearText = () => {
    setChars(0);
    setText("");
  };

  return (
    <div className="App">
      <h1>HTML Progress Element</h1>
      <label htmlFor="textarea-progress">Characters typed</label>
      <progress id="textarea-progress" max="100" value={chars} />
      <span className="char-span">{chars}/100</span>
      <form className="form-wrapper" action="/action_page.php">
        <label className="textarea-label" htmlFor="textarea">
          Start typing below...
        </label>
        <textarea
          type="text"
          id="textarea"
          name="textarea"
          rows="6"
          maxLength="100"
          value={text}
          onChange={handleCharsAndText}
        />
      </form>
      <button onClick={clearText}>Reset</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Breaking It Down

Let's start with the essential parts

<label htmlFor="textarea-progress">Characters typed</label>
<progress id="textarea-progress" max="100" value={chars} />
Enter fullscreen mode Exit fullscreen mode

A progress element must have a max attribute, which is the number at which the bar reaches "100%".

max
This attribute describes how much work the task indicated by the progress element requires. The max attribute, if present, must have a value greater than 0 and be a valid floating point number. The default value is 1.

A progress element must also have a value attribute, which is where the bar is visually represented in progress at the moment.

value
This attribute specifies how much of the task that has been completed. It must be a valid floating point number between 0 and max, or between 0 and 1 if max is omitted. If there is no value attribute, the progress bar is indeterminate; this indicates that an activity is ongoing with no indication of how long it is expected to take.

My progress bar has a max="100", but it could be ANY number. It could be 1,000,000 or 3. But in this case I want the meter to be full when it reaches 100 characters. 1 character for each percent of the bar.

value={chars} is how many characters the user has currently typed in, handled by state:

const [chars, setChars] = useState(0);

...
const handleCharsAndText = (e) => {
  setChars(e.target.value.length);
  setText(e.target.value);
};
Enter fullscreen mode Exit fullscreen mode

And then to make sure the user can't type any more characters when the progress bar is 100% full, I give my <textarea> element a maxLength="100" property, which doesn't allow a user to type more than 100 characters.

Wrapping Up

And that's pretty much it! You can get as creative as you want with the progress element.

In fact, I DID.

Do you want to see my Mini Tweets Codesandbox I made? I wanted to explore if styling a progress element was possible, and I kind of got carried away, mimicking the behavior of a Tweet form and how it warns when you you're getting close to the maximum 140 characters.

BONUS CODE - Mini Tweets

Here's the full React component:

import "./styles.css";
import { useState } from "react";

export default function App() {
  const [open, setOpen] = useState(false);
  const [chars, setChars] = useState(0);
  const [tweet, setTweet] = useState("");
  const [tweets, setTweets] = useState([]);

  const openModal = () => {
    setOpen(true);
  };

  const closeModal = () => {
    setOpen(false);
  };

  const charCount = (e) => {
    setChars(e.target.value.length);
    setTweet(e.target.value);
  };

  const handleSubmit = (e) => {
    const now = new Date().toUTCString();
    if (tweet === "") {
      e.preventDefault();
    } else {
      e.preventDefault();
      setTweets([{ text: tweet, date: now }, ...tweets]);
      setTweet("");
      setChars(0);
      setOpen(false);
    }
  };

  return (
    <div className="App">
      <h1>Mini Tweets</h1>

      <button className="tweet-btn" onClick={openModal}>
        Add Mini Tweet
      </button>

      {open && (
        <>
          <div className="overlay" />
          <div className="add-tweet-card">
            <button className="close-btn" onClick={closeModal}>
              <span aria-label="close tweet form">X</span>
            </button>
            <form className="form-wrapper" action="/action_page.php">
              <label className="textarea-label" htmlFor="tweet">
                Whats on your mind?
              </label>
              <textarea
                type="text"
                id="tweet"
                name="tweet"
                rows="4"
                maxLength="80"
                value={tweet}
                className={chars > 70 ? "red-text" : null}
                onChange={charCount}
              />
              <progress
                aria-label={`${80 - chars} remaining characters`}
                id="tweet-progress"
                max="80"
                value={chars}
              />
              <span className="char-span" aria-label={`${chars}/80`}>
                {chars}/80
              </span>
              <input
                type="submit"
                value="Tweet!"
                onClick={handleSubmit}
                className="tweet-btn"
              />
            </form>
          </div>
        </>
      )}

      {tweets.length > 0 && <h2>Your Tweets</h2>}
      {tweets.map((t, index) => (
        <div key={index} className="tweets-card">
          <p>{t.text}</p>
          <p className="tweet-time">{t.date}</p>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And the full CSS file:

body {
  background-color: rgb(19, 19, 19);
}

textarea {
  width: 10rem;
  border-radius: 0.5rem;
  font-size: 1rem;
  background-color: rgb(3, 3, 26);
  color: white;
}

progress {
  margin-top: 1rem;
  border-radius: 2px;
  width: 10rem;
  height: 1rem;
}
progress::-webkit-progress-bar {
  background-color: #1da1f2;
  border-radius: 2px;
}
progress::-webkit-progress-value {
  background-color: rgb(25, 25, 59);
  border-radius: 2px;
  box-shadow: 1px 1px 3px 3px #2ba6f1cc;
}

.textarea-label {
  margin-top: -0.5rem;
  margin-bottom: 1rem;
  font-size: 1.3rem;
}

.App {
  font-family: Arial, Helvetica, sans-serif;
  text-align: center;
  color: rgb(221, 221, 221);
  display: flex;
  flex-direction: column;
  align-items: center;
}

.add-tweet-card {
  position: fixed;
  z-index: 1;
  margin-top: 5rem;
  margin-left: auto;
  margin-right: auto;
  background-color: #032f4b;
  padding: 1rem;
  border-radius: 0.5rem;
  width: 75%;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-shadow: 0px 0px 15px 5px #2997db88;
}

.overlay {
  position: fixed;
  margin: 0;
  top: 0;
  width: 100%;
  height: 100vh;
  background-color: rgba(9, 15, 29, 0.9);
}

.progress-label {
  font-size: 1rem;
  margin-top: 1rem;
  margin-bottom: 0.5rem;
  color: rgba(255, 255, 255, 0.7);
}

.form-wrapper {
  margin-top: 1rem;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.tweet-btn {
  margin-top: 1rem;
  color: black;
  background-color: #1da1f2;
  font-size: 1.25rem;
  border: none;
  padding: 0.5rem;
  border-radius: 0.5rem;
  cursor: pointer;
}

input[type="submit"] {
  -webkit-appearance: none;
}

.close-btn {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background-color: rgba(255, 255, 255, 0.33);
  color: black;
  border: none;
  padding: 0.1rem;
  height: 1.25rem;
  width: 1.25rem;
  border-radius: 1rem;
  font-weight: bold;
  cursor: pointer;
}

.close-btn:hover {
  background-color: rgba(255, 255, 255, 0.8);
}

.tweet-btn:hover {
  color: #1da1f2;
  background-color: white;
}

.red-text {
  color: red;
  font-weight: bold;
}

.tweets-card {
  margin-bottom: 1rem;
  background-color: #1da1f2;
  padding-left: 2rem;
  padding-right: 2rem;
  border-radius: 0.5rem;
  width: 80%;
  display: flex;
  flex-direction: column;
  color: black;
  font-size: 1.1rem;
  text-align: start;
}

.char-span {
  margin-top: 0.5rem;
}

.tweet-time {
  font-size: 0.9rem;
  color: rgba(0, 0, 0, 0.6);
  margin-top: -0.5rem;
}

@media screen and (min-width: 300px) {
  textarea {
    width: 18rem;
  }

  progress {
    width: 15rem;
  }
}

@media screen and (min-width: 600px) {
  textarea {
    width: 18rem;
  }

  .add-tweet-card {
    width: 50%;
  }

  .tweets-card {
    width: 50%;
  }
}

@media screen and (min-width: 1000px) {
  .add-tweet-card {
    width: 25%;
  }

  .tweets-card {
    width: 25%;
  }
}
Enter fullscreen mode Exit fullscreen mode

Styling the <progress> element took some Googling and I found this stackoverflow answer that I used as a guide, tweaking things here and there until I was happy enough.

Conclusion

I hope you learned something about HTML's progress bar; I know I did. The simple version isn't that scary at all, and I hope you feel empowered to try it out for yourself and see what cool and creative things you can make.

Thank you for reading and if you have any questions or insight, just hit me up in the comments below. See you next time!

Top comments (0)