More Navigation Snippets
Progress Stepper — Free HTML CSS JS Wizard Snippet
Progress Stepper · Navigation · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Progress Stepper — Step States, Connecting Lines & Forward/Back Navigation

A progress stepper shows users where they are in a multi-step process — a checkout flow, an onboarding wizard, a form with multiple sections, or a setup guide. It communicates position ("you are on step 2 of 4"), history ("steps 1 is complete"), and future ("steps 3 and 4 remain"). This snippet implements a full stepper with three visual states, connecting line fill animation, and forward/back navigation.
The three step states
Each .step element carries one of three state classes at any time:
.done — completed step: the dot shows a checkmark SVG icon, the background fills with accent colour, and the connecting line after it fills. Steps can be navigated back to from done state.
.active — current step: the dot shows the step number, the border and text use the accent colour, and it has a glow ring via box-shadow.
Pending (no class) — future step: grey border, grey number, dimmed label.
The connecting lines
A .line div sits between each pair of steps. In the default state it is grey. When a step moves from .active to .done, the line after it gains the .done class: background: #6366f1. This fills the connector, visually showing that the path between steps has been traversed.
The JavaScript navigation
nextStep() finds the current active index, removes .active from it and adds .done, adds .done to the line between the current and next step, then adds .active to the next step. prevStep() reverses this: removes .active from current, removes .done from the preceding line, removes .done from the preceding step and adds .active back to it. Back and forward buttons are disabled at the ends via if (i >= steps.length - 1) return and if (i <= 0) return.
Adding more steps
To add a fourth step, add a new .step div and a new .line div before it in the HTML. The JavaScript uses querySelectorAll so it picks up any number of steps and lines automatically.
Connecting to form panels
In a real multi-step form, call nextStep() when the user clicks a Next button on each form panel. Before calling it, validate the current panel's fields. If validation fails, do not advance — show the errors instead.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to trace the class-toggling logic by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how activeIndex uses findIndex against the done/active classes to locate the current step, and why nextStep and prevStep have to touch three elements (the outgoing step, the line, and the incoming step) in a specific order to stay consistent. The same assistant can help optimize it — for instance whether re-querying getSteps and getLines with querySelectorAll on every call is wasteful for a stepper with many steps, or whether the state should be tracked as a single index instead of derived from DOM classes each time. It's also useful for extending the effect: ask it to add per-step validation before advancing, a clickable step to jump directly to a completed step, or a vertical layout variant. 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 multi-step "progress stepper" in plain HTML, CSS, and JavaScript using only classList toggling — no framework, no state library.
Requirements:
- A horizontal row of step elements, each containing a numbered dot and a label, separated by connector line elements between each pair of steps.
- Each step must support exactly three mutually exclusive visual states driven by CSS classes only: a done state (filled accent background, checkmark icon instead of the number, accent-colored label), an active state (accent-colored border with a soft box-shadow glow ring around the dot, dark label), and a default pending state (grey border, grey number, dimmed label).
- Connector lines must independently track a done state that fills them with the accent color once the step before them is completed.
- Write a nextStep function that locates the currently active step via findIndex against the active class, removes active and adds done to it, adds done to the connecting line immediately after it, then adds active to the following step. Write a prevStep function that reverses all three of those changes in the correct order.
- Both functions must guard against moving past the first or last step (do nothing if already at an end) rather than throwing or wrapping around.
- Use querySelectorAll so the logic works for any number of steps and lines without hardcoding indices or a fixed step count.
- Wire a Back and a Continue button to prevStep and nextStep respectively.Step by step
How to Use
- 1Click Next and Back in the previewClick Next to advance through the steps. The dot fills, the line fills, and the next step activates. Click Back to reverse.
- 2Update the step labelsIn the HTML panel, change each .label span text to your actual step names — "Account", "Payment", "Confirm", etc.
- 3Set the initial active stepBy default step 1 starts as .active. Add the .done class to any preceding steps to start mid-flow.
- 4Add more stepsAdd a .line div and a .step div for each additional step. The JS uses querySelectorAll so it picks them up automatically.
- 5Change the accent colourFind #6366f1 in the CSS and replace with your brand colour. Updates the done/active dot fill, line fill, and glow ring.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
nextStep() finds the active step index with findIndex, removes .active from it and adds .done, adds .done to the connecting line at that index, then adds .active to the next step. prevStep() reverses this: removes .active from current, removes .done from the preceding line, removes .done from the preceding step and restores .active to it.
In the HTML panel, add a <div class="line"></div> after the last step, then add a new <div class="step"><div class="dot">4</div><span class="label">Your Step</span></div>. The querySelectorAll selectors pick up any number of .step and .line elements automatically.
Add multiple form panel divs (one per step). Show only the active panel. In nextStep(), validate the current panel before advancing. If validation fails, call e.preventDefault() and show error messages instead of calling nextStep().
In the HTML, add class="done" to step 1 and class="done" to the line after it. Add class="active" to step 2 (instead of step 1). Remove .active from step 1.
Change .stepper to display: flex; flex-direction: column; align-items: flex-start. Change .line to width: 2px; height: 32px and remove its flex-shrink. Change .step to flex-direction: row so the dot and label sit side by side.
Yes. Click "JSX" to download a React component. In React, manage a currentStep number with useState. Derive the state of each step from the index relative to currentStep: index < currentStep is done, index === currentStep is active, index > currentStep is pending.