Circular Step Indicator — Free HTML CSS JS Snippet

Circular Step Indicator · Navigation · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

SVG stroke-dashoffset rings
Each step circle uses an SVG <circle> with animated stroke-dashoffset driven by CSS transitions — no canvas, no icon font, pure SVG math.
Three visual states
Inactive (grey, empty ring), active (indigo, 75% ring), and done (green, full ring + check icon) — each with distinct colour and icon transitions.
Animated check-mark crossfade
The step number and check icon share the same circle space. On completion, the number fades out while the SVG polyline check scales in via a compound CSS transition.
Colour-coded connector lines
Connector divs between steps change from grey to indigo with a background CSS transition as steps complete, giving a visual trail of progress.
Animated panel transitions
Each content panel enters with a fadeUp keyframe animation (opacity + translateY), creating a smooth forward-motion feel as users progress.
Back and Next navigation
The Back button is disabled on step 1 and re-enables once you advance. The Next button transforms into "Finish" on the last step.
Zero dependencies
Pure HTML, CSS, and vanilla JS — no React, no Vue, no animation library. The complete ring math is a single formula: C * (1 - fraction).
Accessible markup
Steps use data attributes for state; panels are plain divs. Extend with aria-current="step" on the active step and role="tabpanel" on panels for full ARIA support.

About this UI Snippet

Circular Step Indicator — SVG Ring Progress, Animated Connectors & Multi-Step Wizard in Vanilla JS

Screenshot of the Circular Step Indicator snippet rendered live

A multi-step wizard tells users exactly where they are in a process, how far they've come, and what remains — reducing drop-offs on long registration, checkout, and onboarding flows by giving a visual contract up front. This snippet delivers a polished four-step wizard with circular SVG ring progress indicators, animated fill arcs, check-mark transitions, colour-coded connector lines, and a slide-in panel system — all in plain HTML, CSS, and vanilla JavaScript with zero dependencies.

A multi-step wizard is one of the highest-value UI components in any product that involves user data entry. When users can see that they're on step 2 of 4, completion rates measurably increase compared to a single long form — the progress indicator converts an open-ended task into a finite sequence. This snippet delivers a polished four-step wizard with circular SVG ring progress indicators, animated stroke arcs, check-mark reveal transitions, colour-changing connector lines between steps, content panel switching, and Back/Next navigation — all built in plain HTML, CSS, and vanilla JavaScript with zero dependencies.

SVG stroke-dasharray / stroke-dashoffset animation technique

The ring around each step number is a <circle> element inside an SVG. The circle has stroke-dasharray: 113 — that's the circumference of the circle (2π × r = 2π × 18 ≈ 113.1 px). By animating stroke-dashoffset from 113 (fully hidden) toward 0 (fully drawn), CSS creates the impression of a stroke drawing itself around the circle. The inactive state uses an offset of 113 (no stroke visible), the active state uses ~28 (showing ~75% of the ring), and the completed state uses 0 (full ring). A single CSS transition: stroke-dashoffset 0.5s ease animates between all three states smoothly whenever a class changes. The SVG is rotated −90° with CSS so the stroke begins at 12 o'clock rather than the default 3 o'clock position.

Check-icon reveal and state layering

Each step circle contains two overlapping elements: a <span class="step-num"> showing the number, and a hidden check SVG. When a step gains the done class, the number fades out (opacity:0) while the check icon scales from 0.6 to 1 and fades in — a compound transform + opacity transition. Both elements are absolutely positioned inside the circle wrapper so they occupy the same space; CSS transition handles the crossfade without any JavaScript timing code. The check icon uses a <polyline points="20 6 9 17 4 12"> path which draws the classic "√" tick.

Connector line state

The horizontal lines between step circles are simple <div class="connector"> elements. In the CSS they are flex:1 — they stretch to fill the gap between circles. Their background colour transitions between #e2e8f0 (grey, incomplete) and #6366f1 (indigo, complete) as steps are completed. The render() function applies done to each connector at index i when i + 1 < current, meaning connector 0 (between steps 1 and 2) turns indigo once step 2 is current.

Panel switching with CSS animation

Content panels are display:none by default and switch to display:flex when the active class is applied. A @keyframes fadeUp animation (opacity 0→1, translateY 8px→0) plays on each newly active panel, giving the wizard a light upward wipe between steps. Because the animation is applied via the class name and the panel switches to display:flex simultaneously, no JavaScript timeout or requestAnimationFrame trick is needed.

Customising for real forms

Swap the placeholder <input> fields for your actual form fields. For validation, check field values inside the btnNext click handler before incrementing current — return early and highlight invalid fields if needed. The step labels ("Account", "Profile", "Billing", "Done") are plain text nodes in the HTML; change them to match your wizard's stages. To add more steps, duplicate a .step, a .connector, and a .panel, increment TOTAL, and the ring math handles the rest. Pair with a vertical timeline for a history view after the wizard completes.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Instead of assuming the ring math is self-evident, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the constant C (2 times PI times 18) relates to the hardcoded stroke-dasharray value of 113 in the CSS, and why the active step deliberately shows a 75 percent filled ring rather than either 0 or 100 percent. The same assistant can help you harden it — ask whether render() correctly handles a form validation failure (what should happen to the ring, connector, and panel state if a user tries to advance past an incomplete step), since the current click handler always advances. It's also a good partner for extending the wizard: ask it to add per-step validation that blocks the Next click when required fields are empty, animate the connector line filling left-to-right instead of snapping between two colors, or make the whole step indicator independently clickable so users can jump back to any completed step. 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 multi-step "circular step indicator" wizard in plain HTML, CSS, and JavaScript using inline SVG rings — no library.

Requirements:
- A horizontal row of numbered step circles connected by flex-grow connector line divs between them, where each step circle contains a background SVG ring (a static gray circle), a foreground progress SVG ring (using stroke-dasharray set to the circle's exact circumference and stroke-dashoffset to control fill amount) rotated -90 degrees, a numeral, and a hidden checkmark icon layered on top of each other in the same space.
- Each step must render in exactly one of three distinct states derived from comparing its own step number to the current step number: not-yet-reached (empty ring, gray numeral, no connector highlight), currently-active (ring filled to a partial percentage like 75 percent, accent-colored numeral), and completed (ring fully filled, numeral faded out, checkmark faded and scaled in, connector after it colored to indicate progress).
- The crossfade between the numeral and the checkmark on a completed step must use only opacity and transform (scale) transitions on two absolutely-positioned overlapping elements — no JavaScript-driven timing or swapping of DOM content.
- Below the step row, render one content panel per step where only the panel matching the current step is displayed (using a display toggle, not just visibility), and give the newly shown panel an entrance animation (fade plus slight upward translate) via a CSS keyframe rather than a JavaScript animation library.
- Wire Back and Next buttons where Back is disabled on the first step, Next's label changes to a distinct final action label (e.g. "Finish") on the last step, and clicking Next advances the current step and re-renders every step circle, connector, and panel from that single current-step variable rather than manipulating individual DOM classes ad hoc.
- Make the ring math derive from a single circle radius so that changing the radius (and the corresponding CSS circle radius) is the only thing needed to resize the whole ring system consistently.

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

  1. 1
    Load the snippetPaste the HTML, CSS, and JS into your page. The wizard renders with step 1 active, showing a 75% arc ring around the number 1 and the Account panel.
  2. 2
    Click Next to advanceThe current step's ring completes to a full green circle with a check icon, the connector line turns indigo, and step 2 activates with the Profile panel fading up.
  3. 3
    Continue through all stepsEach completed step shows a full green ring and check mark. The connector between completed steps is indigo; future steps remain grey.
  4. 4
    Use Back to returnBack reverses the animation — the last completed step loses its check icon and returns to the active ring state, the connector reverts to grey.
  5. 5
    Finish on step 4The Next button reads "Finish" on the last step. On click, a success alert fires and the wizard resets to step 1 — ready for another submission in demos.
  6. 6
    Customise labels and stepsEdit the .step-label text in HTML and <h2> headings in each panel. Add validation inside the btnNext handler before incrementing current.

Real-world uses

Common Use Cases

User registration wizards
Break a lengthy sign-up form into Account, Profile, Preferences, and Done steps so users see progress instead of a wall of fields. Combine with a stepper for linear form flows.
Checkout flows
Guide shoppers through Cart → Shipping → Payment → Confirmation with visual progress rings that reduce checkout abandonment. Pair with a pricing toggle on the payment step.
Onboarding tours
Walk new users through setup tasks with a visual step counter. For an overlay version that highlights UI elements, see the onboarding tour snippet.
Multi-page surveys
Show respondents how many sections remain in a long survey, reducing abandonment. Each section maps to one step with a custom label.
Installation wizards
Guide users through software or service setup steps (Connect, Configure, Test, Launch) with clear visual confirmation as each stage completes.
Project setup flows
SaaS products can use the wizard for workspace creation — name your workspace, invite teammates, choose a plan, go live. Each ring completion reinforces progress.

Got questions?

Frequently Asked Questions

Duplicate a .step div, a .connector div, and a .panel div in the HTML, then increment TOTAL in the JS. The ring math derives from TOTAL automatically — no further changes needed.

Yes. In the btnNext click handler, add a guard before current++: check your fields, add an error class if invalid, and return early. Only call current++; render() once validation passes.

The active state sets stroke-dashoffset to 28 (≈75% filled). Change this in the setRingProgress call for the active step: e.g., setRingProgress(ring, 0.5) for 50%. The fraction argument maps directly to the visible arc percentage.

Yes. Use the JSX, Vue, Angular, or Tailwind export buttons on this page. In React, keep currentStep in useState and derive each step's state (inactive/active/done) during render. Move the ring fraction math into a helper function and pass the result as an inline SVG style prop — the CSS transition animates the change automatically. In Vue, use computed properties for step states; in Angular, use a @Pipe or template expression.

conic-gradient produces sharp edges and cannot be animated with CSS transitions in most browsers. SVG stroke-dashoffset animates natively with transition and produces smooth anti-aliased arcs at any size. It's also easier to control the start angle (rotate the SVG −90°) and stroke cap style (stroke-linecap: round).