Progress Steps
Numbered step indicator: the current step lights up indigo, previous ones turn green and the line linking them fills in.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Data has 28 effects, including 1 free. Explore the category →
3 usage examples



How it works
The steps form an ordered list <ol class="pgs-list">; each <li class="pgs-step"> holds a .pgs-dot and a .pgs-label. The connecting lines are not extra elements: they are the ::before (grey rail) and ::after (green-to-indigo fill) pseudo-elements of every step except the first.
The current step is read from the root's data-current attribute. The script sets .pgs-done on earlier steps, .pgs-current and aria-current="step" on the current one; the CSS colours the dot (green, or indigo with a glow) and stretches the line fill (scaleX, 0.6 s). With data-current set to the number of steps plus one, all of them are completed.
One MutationObserver per instance watches data-current: root.setAttribute('data-current', 3) is enough to move forward or back. When the indicator scrolls into view, it starts from the first step and fills the next ones one by one (0.25 s apart) up to the current step.
The number of steps is up to you; they share the width (up to --pgs-max-width, 420 px) and long labels wrap under their dot.
Accessibility
- Ordered list and current step: a screen reader announces "list, 4 items", then each step with its number and label; the current step carries
aria-current="step". - State in words, not only colour: completed steps get visually hidden text "(completed)" ("(terminée)" on a French page), so green is not the only cue.
- Reduced motion: under
prefers-reduced-motion: reduce, colours and lines change without transition and the final state shows as soon as it appears. - An indicator, not navigation: steps are not clickable. If users must be able to go back to a step, put links or buttons inside the labels and handle them yourself.
Browser compatibility
Flexbox, pseudo-elements, transform and colour transitions, MutationObserver, IntersectionObserver. Measured in Chromium 151, Firefox 153 and WebKit 26.5: two independent indicators on one page (changing one leaves the other alone), line half-filled at 150 ms, 390 px, white background, no JS error.
Without JavaScript all dots and labels show in grey, with no highlighted step: the list stays readable.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0248-root" data-current="2">
<ol class="pgs-list" aria-label="Order steps">
<li class="pgs-step"><span class="pgs-dot">1</span><span class="pgs-label">Cart</span></li>
<li class="pgs-step"><span class="pgs-dot">2</span><span class="pgs-label">Info</span></li>
<li class="pgs-step"><span class="pgs-dot">3</span><span class="pgs-label">Pay</span></li>
<li class="pgs-step"><span class="pgs-dot">4</span><span class="pgs-label">Done</span></li>
</ol>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
data-current (HTML, on the root) |
2 | Number of the current step (1 = the first). The number of steps plus one marks everything as completed. |
| Steps (HTML) | 4: Cart, Info, Pay, Done | Add or remove <li class="pgs-step"> items; numbers and labels are free. |
--pgs-active, --pgs-glow (CSS) |
#6366f1, rgba(99,102,241,.5) | Colour and glow of the current step. |
--pgs-done, --pgs-fill (CSS) |
#10b981, green → indigo gradient | Colour of completed steps and of the filled line. |
--pgs-idle, --pgs-idle-border, --pgs-text, --pgs-text-on (CSS) |
8% white, 15% white, 40% white, 80% white | Upcoming dots and labels; replace them with dark tones on a light background. |
--pgs-dot-size, --pgs-max-width (CSS) |
36px, 420px | Dot size (lines realign on it) and maximum width. |
--pgs-speed (CSS) |
.6s | Duration of a line fill. |
--pgs-bg, --pgs-min-height (CSS) |
#0a0a0f, 300px | Root background and minimum height. |
FAQ
indicator.setAttribute('data-current', number). The script observes the attribute; there is no global function to call and several indicators can live side by side.data-current to the number of steps plus one (5 for four steps): every dot turns green and every line is filled, with no step marked as current.--pgs-max-width; on mobile, five steps with labels over ten letters become hard to read.