You Might Also Like
Progress Circle Steps — Free HTML CSS JS Step Progress Bar
Progress Circle Steps · Loaders · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Progress Circle Steps — A Numbered Wizard Progress Bar

The progress circle steps indicator is the horizontal "1 — 2 — 3 — 4" tracker at the top of checkouts and multi-step forms, where completed steps turn into green check circles and the connector between them fills in as you advance. This snippet builds it with plain HTML, CSS, and a small vanilla JavaScript state machine.
Nodes and connectors
Each step is a flex column holding a circular node and a label. The connecting line is a ::after on every step except the last, positioned absolutely at the node's vertical centre and stretched across the gap behind the circles (z-index: 0 so the nodes sit on top). When a step is marked done, its connector turns green — so the line literally fills up to the current step, giving an at-a-glance sense of progress.
Three node states from classes
A step is in one of three states driven entirely by CSS classes: upcoming (muted circle with its number), current (accent border and a soft focus ring around the node), or done (solid green circle showing a check mark instead of the number). The check is a ::after content swap with the number's font-size set to zero, so the same node element morphs from a digit to a tick without extra markup. Smooth transition: all on the node makes each change animate.
A tiny state machine
JavaScript holds a single current index and a render() function that re-derives every step's classes from it — done if before current, current if equal, upcoming otherwise — plus the button states. This is the cleanest way to drive a stepper: one number is the source of truth, and the whole indicator is a pure function of it, so there's no chance of two steps both appearing current.
Navigation, including back-jumps
Next advances the index (and the final Next becomes "Finish"), Back decrements it and disables at the first step, and clicking any node at or before the current step jumps straight back to it — letting users revisit a completed step without clicking Back repeatedly. Forward jumps to undone steps are blocked, so you can't skip ahead past required steps.
Accessible structure
The labels are real text under each node, so the steps are readable and not conveyed by colour alone. The disabled Back button uses the native disabled attribute, and the markup is plain enough to extend with aria-current="step" on the active step for screen readers.
Customizing it
Add or remove steps freely — the flex layout and index logic adapt — change the accent and done colours, or wire render() to also show the matching form panel for each step. Pair it with a multi-step form, a progress wizard, or a stepper.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to work out the class-driven state machine by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how render() derives is-done, is-current, and the default upcoming state purely from comparing each step's index to the current variable, or how the ::after connector line and the number-to-checkmark swap both key off those same two classes with no extra markup. The same assistant can help optimize it, for example checking whether re-toggling every step's classList on every render call matters once there are dozens of steps, or whether the connector's absolute positioning holds up if labels wrap to two lines. It's just as useful for extending the stepper: ask it to add a shake animation when a forward jump is blocked, persist the current step to the URL or localStorage so a reload resumes progress, or support a vertical orientation for narrow screens. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a horizontal numbered "progress circle steps" indicator in plain HTML, CSS, and JavaScript with no framework and no libraries.
Requirements:
- A row of step elements, each containing a circular numbered node and a text label, laid out with flexbox so each step takes equal width.
- A connecting line between each pair of adjacent nodes, implemented as a pseudo-element positioned behind the nodes (lower z-index) and stretched across the horizontal gap, that changes color to indicate completed progress.
- Exactly one JavaScript variable holds the current step index. A single render function must be the only place that assigns per-step CSS classes: mark a step done if its index is less than current, current if equal, and leave it in a default upcoming state otherwise — never toggle these classes anywhere else in the code.
- A done step's circular node must swap its visible content from the step number to a checkmark using only CSS (for example, zeroing the number's font-size and adding checkmark content via a pseudo-element keyed off the done class) — no DOM element swapping.
- A Next button that advances current by one (relabeling itself "Finish" on the second-to-last step) and a Back button that decrements it and gets the native disabled attribute at the first step.
- Clicking directly on any step's node must jump current to that step's index, but only if that step's index is less than or equal to the current index — clicking a future, not-yet-reached step must do nothing, so users can revisit completed steps but never skip ahead.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA four-step tracker renders at step two.
- 2Click NextThe current step completes and fills the line.
- 3Reach the endThe Next button label becomes Finish.
- 4Click BackIt steps back and disables at the first step.
- 5Click a past nodeJump straight to any completed step.
- 6Add a stepDrop in another .ps-step — logic adapts.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Each step except the last has a ::after line positioned behind the nodes. When a step is marked done its connector turns green, so the line fills up to the current step. Because done is derived from the current index, the fill always matches how far the user has advanced.
The done node keeps the same element but sets the number's font-size to zero and adds a check as ::after content, so the circle morphs from a digit to a tick without extra markup. A transition on the node animates the colour and border change smoothly as a step completes.
A single current index is the source of truth, and a render function re-derives every step's classes from it — done before current, current when equal, upcoming after — plus the button states. Because the whole indicator is a pure function of one number, two steps can never both appear current.
Yes. Clicking any node at or before the current step sets the index to it, so users can revisit a completed step without repeatedly clicking Back. Forward jumps past undone steps are blocked, so the guard prevents skipping required steps while still allowing free backward navigation.
Hold the current step index in state and render each step's done/current classes from it, exactly like the render function. Wire Next and Back to increment and decrement the index, and node clicks to jump back when the index is less than or equal to current. The node, connector, and check CSS port unchanged.