Creating a simple progress bar with just CSS: interactive and without JavaScript

- 馃懁 Andr茅s Cruz

馃嚜馃嚫 En espa帽ol

Creating a simple progress bar with just CSS: interactive and without JavaScript

Continuing with some CSS experiments, today I want to show how to create a progress bar using only CSS, completely interactive and without the need for JavaScript. This isn't just an automatic animation, but a progress bar with real states, controlled by the user and managed solely with HTML and CSS.

In these types of tests, I usually modify several aspects of the original code, but in this case, the approach worked so well from the start that I decided to keep its main logic: using radio buttons as a state system and letting CSS do all the work.

What is a progress bar and when to use only CSS?

A progress bar is a visual component that indicates the progress of a process: loading, completed steps, reached level, etc. It is normally associated with JavaScript, but it is not always necessary.

Differences between a CSS progress bar and one with JavaScript

When you use JavaScript, progress usually depends on real dynamic data (uploads, downloads, calculations). In contrast, a progress bar made only with CSS is ideal when:

  • Progress is visual or demonstrative
  • States are predefined
  • You seek simplicity and performance
  • You are creating prototypes or CSS experiments

In my tests, this type of solution is perfect when you don't need complex logic and want to keep the code as clean as possible.

Advantages and limitations of using only CSS

  • Advantages
    • No dependency on scripts
    • Better performance
    • Easier code maintenance
    • Ideal for learning advanced selectors
  • Limitations
    • No real dynamic progress
    • States must be previously defined

Continuing with some CSS experiments, today we will see how to create an interactive progress bar exactly as you can see in the promotional image for this post.

How to create a progress bar with only CSS step by step

Actually, building this progress bar is quite simple. Like many other experiments that have different statuses using only CSS, radio buttons are used as the central element; this is the HTML structure:

<input type="radio" name="progress" id="five">
<label for="five">5%</label>

<input type="radio" name="progress" id="twentyfive" checked>
<label for="twentyfive">25%</label>

<input type="radio" name="progress" id="fifty">
<label for="fifty">50%</label>

<input type="radio" name="progress" id="seventyfive">
<label for="seventyfive">75%</label>

<input type="radio" name="progress" id="onehundred">
<label for="onehundred">100%</label>

<div class="progress">
  <div class="progress-bar"></div>
</div>

An interesting point is that interaction happens only with the labels and these with the radio (which are hidden). This is achieved thanks to the for attribute established in the label whose value must be equal to the id element of the radio so they can work together.

Markup organization so CSS can react

The order of the HTML is fundamental. We use the ~ combinator so that, when a radio button is active, CSS can modify the progress bar that appears later in the DOM.

As you can see, each of the radios corresponds to a status of the progress bar, which we vary when clicking on the statuses with the following CSS:

#five:checked ~ .progress > .progress-bar {
	width: 5%;
	background-color: #f34213;
}
#twentyfive:checked ~ .progress > .progress-bar {
	width: 25%;
	background-color: #f27011;
}
#fifty:checked ~ .progress > .progress-bar {
	width: 50%;
	background-color: #f2b01e;
}
#seventyfive:checked ~ .progress > .progress-bar {
	width: 75%;
	background-color: #f2d31b;
}
#onehundred:checked ~ .progress > .progress-bar {
	width: 100%;
	background-color: #86e01e;
}

The rest of the CSS is just to apply styles to the progress bar, label, body, etc.

Controlling progress with pure CSS

Using the :checked selector to change the state

Each state is controlled with a specific CSS rule:

#five:checked ~ .progress > .progress-bar {
 width: 5%;
 background-color: #f34213;
}

The radios function as a state system without JavaScript, which is very powerful for these types of components.

CSS combinators applied to the progress bar

  • ~ allows affecting sibling elements
  • > ensures only the correct element is modified

This combination avoids side effects and maintains total control over the style.

Assigning real percentages without JavaScript

Each radio defines a specific percentage:

#fifty:checked ~ .progress > .progress-bar {
 width: 50%;
 background-color: #f2b01e;
}

It is not a fake animation: the width actually changes according to the active state.

Styles and animation of the CSS progress bar

The rest of the CSS is purely visual, but important for the experience. We adjust the width, colors, and transitions:

.progress {
 width: 100%;
 background: #eee;
 border-radius: 4px;
}
.progress-bar {
 height: 20px;
 width: 0;
 transition: width 0.4s ease;
}

Difference between automatic animation and interactive progress

Unlike many loading bars animated with @keyframes, here the user controls the progress. That makes the component more useful in real contexts like step-by-step forms.

Real use cases for a progress bar without JavaScript

  • Step-by-step forms
    • Perfect for indicating which step the user is on.
  • Visual status indicators
    • Simulated processes, levels, configurations.
  • Prototypes and CSS experiments
    • Ideal when you want to validate a quick idea without adding extra logic.

Frequently asked questions about CSS-only progress bars

  • Can a progress bar be made without JavaScript?
    • Yes, as long as the progress is based on predefined states.
  • Is it recommended for production?
    • It depends on the case. For real progress, no. For visual states, yes.
  • Can it be animated?
    • Yes, using transition or @keyframes, although here the focus is on interaction.

Conclusion: when to use a pure CSS progress bar

Creating a progress bar with only CSS is not only possible but very useful in certain contexts. This approach demonstrates how far you can go by understanding selectors like :checked, the relationship between label and input, and CSS combinators.

If the goal is to learn, experiment, or build lightweight and controlled components, this solution is more than enough. Additionally, it is an excellent way to dive deeper into CSS beyond the basics.

Complete example of an interactive CSS-only progress bar

The final result is a progress bar that responds instantly to a click, without a single line of JavaScript. In my experiments, this type of solution is ideal for demos, documentation, and advanced CSS learning.

With this, we obtain the following result:

I agree to receive announcements of interest about this Blog.

Learn how to create an interactive progress bar using only CSS and no JavaScript. A practical, step-by-step example using radio buttons and CSS selectors.

| 馃懁 Andr茅s Cruz

馃嚜馃嚫 En espa帽ol