Multi-Step Progress Indicator — HTML CSS JS

Multi-Step Progress Indicator · Navigation · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Three states: completed (filled + checkmark), active (glow ring), pending (gray)
Connector fill animates via height: 0→100% CSS transition
connector left: 17px aligns line to circle center mathematically
Click-to-navigate allows backward navigation but not skip-ahead
CONTENT array maps step index to contextual instructions
Finish state disables button and shows completion message
check-icon and step-num toggle via display via class
box-shadow ring on active step for focus emphasis

About this UI Snippet

Multi-Step Progress Indicator — Completed/Active/Pending States & Animated Connector Fill

Screenshot of the Multi-Step Progress Indicator snippet rendered live

Multi-step progress indicators, also called steppers or breadcrumb flows, are navigation components that show a user's position within a multi-page process — checkout flows, onboarding wizards, form sequences, and setup flows. Clear visual progress reduces abandonment by giving users a sense of completion and a clear endpoint. This snippet builds a vertical stepper with animated connector fills, three distinct visual states, click-to-navigate completed steps, and a content panel that updates per step.

Three-state visual system

The stepper uses three CSS classes to represent progression: .completed (steps already done), .active (the current step), and .pending (future steps not yet reached). These map to distinct visual treatments: - Completed: filled indigo circle with a white checkmark SVG icon, full connector fill - Active: filled indigo circle with white step number, a glowing ring shadow (box-shadow: 0 0 0 4px rgba(99,102,241,.18)) - Pending: empty white circle with gray border, dimmed label text

The JS updates these classes on every state change using classList.remove('completed','active','pending') followed by the appropriate classList.add() — a clean state reset pattern.

Animated connector fill

The connecting line between steps is built with two nested elements: .connector (the gray track) and .connector-fill (the indigo progress). The connector is position:absolute; left:17px; top:36px; width:2px; height:calc(100% - 36px) — positioned to run from the bottom of the circle (36px from the step top) to the bottom of the label area. The fill starts at height:0% and transitions to height:100% when the step's .completed class is applied. The transition:height .4s ease creates a smooth vertical fill animation as steps are completed.

The left:17px positions the line at the horizontal center of the 36px-wide circle (36/2 - 1 = 17, accounting for the 2px line width). This mathematical alignment ensures the line appears to grow from the bottom of the circle, not offset to one side.

Click-to-navigate completed steps

Each step element has a click event listener. The handler only navigates if i <= current — users can click backward to any completed step but cannot skip ahead to pending steps. This matches the UX convention for steppers: you can always go back to review/edit, but you cannot jump ahead. The click handler sets current = i and calls updateStepper(), making completed steps act like navigation links.

Content panel per step

Below the stepper, a content panel displays contextual instructions for the current step. The CONTENT array maps step index to description text. On every state change, content.querySelector('.content-text').textContent = CONTENT[current] updates the panel instantly. A real implementation would animate this update with a fade or slide transition to signal that the content changed.

Finish state handling

When the last step is active and the user clicks "✓ Finish", the button text changes and a completion message replaces the content panel. In a real application, the finish action would submit the form, call an API, or navigate to a success page. The button is then disabled to prevent double submission.

React pattern

In React: const [step, setStep] = useState(0); const steps = [{ title, sub, content }, ...]. The stepper renders a steps.map((s, i) => <Step state={i < step ? 'completed' : i === step ? 'active' : 'pending'} onClick={() => i <= step && setStep(i)} />). The connector fill uses style={{ height: i < step ? '100%' : '0%' }} with a CSS transition. Navigation buttons update the step state.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Have an AI coding assistant like Claude explain the exact math behind the connector's left:17px positioning and why it has to change if you resize the 36px step circles, or why updateStepper() removes all three state classes before adding one back rather than toggling each individually. It is also useful for spotting rough edges — ask whether the content panel swap should animate instead of snapping, or whether the click handler's "i <= current" guard has any edge cases when a user double-clicks fast. For extending the component, ask for per-step validation that blocks Continue until a condition is met, a horizontal layout variant, or a loading spinner state inside the active circle while an async save runs. 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:

text
Build a vertical multi-step progress stepper in plain HTML, CSS, and JavaScript with no framework or library.

Requirements:
- Render a fixed sequence of steps, each with a circle, a title, a subtitle, and a connecting line to the next step, and track the current step index in a single JS variable.
- Every step must resolve to exactly one of three states — completed, active, or pending — computed purely from comparing its index to the current index, and each state must apply a distinct class via classList.remove of all three state classes followed by classList.add of the correct one.
- Completed steps show a checkmark icon and hide the step number; active and pending steps show the step number, with the active circle getting an outer glow via box-shadow.
- The vertical connector between two steps must be built from two stacked elements — a static gray track and a colored fill — where the fill's height animates from 0% to 100% via a CSS transition when the earlier step becomes completed.
- Clicking a step must only navigate backward or to itself (index less than or equal to the current step) — clicking a future pending step must do nothing.
- A content panel below the stepper must show step-specific instructional text pulled from an array indexed by the current step, updating whenever the step changes.
- Provide Back and Continue/Finish buttons where Back is disabled on the first step and Continue relabels to a finish state and disables itself after being clicked on the last step.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA vertical 4-step stepper appears. Step 1 is completed (checkmark), Step 2 is active (glowing circle), Steps 3–4 are pending (gray).
  2. 2
    Click "Continue"Step 2 becomes completed, its connector line fills with indigo, Step 3 becomes active. The content panel updates with Step 3 instructions.
  3. 3
    Click a completed stepClicking Step 1's circle navigates back to it. Steps 2+ revert to pending. The content panel shows Step 1's instructions.
  4. 4
    Reach the last stepThe "Continue" button changes to "✓ Finish". Clicking it shows a completion message and disables the button.
  5. 5
    Customize step labelsUpdate the step-title and step-sub spans in HTML. Update the CONTENT array in JS with matching instructions for each step.

Real-world uses

Common Use Cases

Checkout and payment flows
E-commerce checkouts use steppers for Cart → Shipping → Payment → Review → Confirm — keeping users oriented in a multi-screen purchase process.
Onboarding wizards
SaaS apps use onboarding steppers to guide new users through account setup, profile creation, team invites, and feature discovery.
Multi-step form sequences
Long forms (job applications, loan applications, insurance quotes) break into steps to reduce cognitive load and improve completion rates.
Course and tutorial progress
Online courses show lesson progress as a stepper — completed lessons show checkmarks, the current lesson is highlighted, upcoming lessons are dimmed.
Installation and setup wizards
Software installers, plugin setup flows, and integration configuration wizards use steppers to walk users through multi-stage setup processes.

Got questions?

Frequently Asked Questions

Change .stepper to display:flex; flex-direction:row; align-items:flex-start. Change .connector to position:absolute; top:17px; left:36px; height:2px; width:calc(100% - 36px). Change .connector-fill to height:100%; width:0% and transition width instead of height. The step labels go below the circles.

In nextStep(), run your validation logic before incrementing current. If invalid, show an error message in the content panel and return early. You can highlight the invalid step with an error class: step.classList.add('error') with a red circle style.

Create <Stepper steps={steps} currentStep={step} onStepChange={setStep} />. Each step object has { title, subtitle, content }. The component renders the connector fill with style={{ height: i < currentStep ? '100%' : '0%' }}. Navigation is handled by parent state, making the stepper a controlled component.

Add an isLoading state. In nextStep(), set loading to true, make your async call (API validation, save to server), then set current++ and isLoading = false. While loading, show a spinner in the active step circle and disable both navigation buttons.

Open the Export menu (or the Test Exports preview) in the snippet toolbar. It generates a plain React component, a React + Tailwind version where the step-circle and connector styles become utility classes, a Vue 3 single-file component, and an Angular standalone component. Each converter preserves the markup, the connector-fill animation, and the click-to-navigate behaviour, so the stepper works identically across React, Vue, and Angular. Keep the current step in component state and pass the steps array in as a prop or input rather than hardcoding the panels in the markup.