Source Code

<div class="ms-page"><button type="button" class="ms-open" id="msOpen">Set up workspace</button></div>

<div class="ms-backdrop" id="msBackdrop"></div>
<div class="ms-modal" id="msModal" role="dialog" aria-modal="true" aria-label="Setup wizard">
  <div class="ms-dots" id="msDots"></div>
  <div class="ms-viewport"><div class="ms-track" id="msTrack">
    <section class="ms-step"><h3>Welcome 👋</h3><p>Let us set up your workspace in three quick steps.</p></section>
    <section class="ms-step"><h3>Name it</h3><p>Pick a workspace name.</p><input type="text" placeholder="Acme Inc." class="ms-input"></section>
    <section class="ms-step"><h3>Invite team</h3><p>Add teammate emails (optional).</p><input type="text" placeholder="email, email…" class="ms-input"></section>
    <section class="ms-step ms-final"><div class="ms-check">✓</div><h3>All set!</h3><p>Your workspace is ready to go.</p></section>
  </div></div>
  <div class="ms-nav">
    <button type="button" class="ms-btn ms-ghost" id="msBack">Back</button>
    <button type="button" class="ms-btn" id="msNext">Next</button>
  </div>
</div>

Multi-Step Modal — Wizard Dialog HTML CSS JS

Multi-Step Modal · Modals · Plain HTML, CSS & JS · Live preview

What's included

Features

Sliding step track
Steps sit on a flex track and slide horizontally via a translateX transform.
Inputs preserved
All steps stay in the DOM, so typed input survives moving back and forth.
Progress dots
Active (pill), done (tinted), and upcoming dots show position and history.
Single-index state
One step index drives the track, dots, and controls for consistency.
Adaptive navigation
Back disables on step one; Next becomes Finish then Done on the last step.
Success finish
The final step is a check-mark confirmation, the satisfying wizard ending.
Modal conventions
role=dialog, backdrop dim, scale-and-fade open, close on backdrop/Escape.
Auto-counted & no library
Dots and navigation derive from the step count in plain HTML/CSS/JS.

About this UI Snippet

Multi-Step Modal — A Sliding Wizard Dialog with Progress Dots and Step Controls

Screenshot of the Multi-Step Modal snippet rendered live

A multi-step modal walks the user through a short flow — onboarding, setup, a guided form — inside a dialog, one step at a time, with progress dots and back/next controls. It keeps the user focused on a single decision per screen rather than facing a long form. This snippet builds it in plain HTML, CSS, and vanilla JavaScript: horizontally-sliding steps, animated progress dots, smart navigation, and a success finish — no library.

Steps slide on a track

All steps live side by side on a flex track inside an overflow: hidden viewport, each step min-width: 100%. Advancing translates the track by -step × 100%, so steps slide horizontally like a carousel. Building the wizard as one sliding track (rather than toggling display per step) gives a smooth animated transition between steps and keeps all step content in the DOM, which preserves any input the user has typed as they move back and forth.

Progress dots that show position and history

A row of dots reflects progress: the current step's dot widens into a pill (active), completed steps' dots are tinted (done), and upcoming ones stay grey. Showing both where you are and how far you've come orients the user in the flow — they can see it's a short, finite process, which reduces the abandonment a long unmarked form causes. The dots are generated from the step count, so they always match.

Navigation that adapts

One step index drives everything through update(): the track position, the dot states, the disabled state of Back (off on the first step), and the Next button's label — which becomes "Finish" on the penultimate step and "Done" on the last, where it closes the wizard. Deriving the whole UI from a single index keeps the navigation consistent and makes the button semantics (you can't go back from step one; the last action closes) clear.

A polished modal shell

The dialog is role="dialog" with aria-modal, dims the page behind a backdrop, and opens with a subtle scale-and-fade. It resets to step one each time it opens, and closes via the backdrop, Escape, or finishing — all standard modal conventions. The final step is a success state with a check, the satisfying "you're done" confirmation a wizard should end on.

Drop-in and adaptable

Add or remove <section class="ms-step"> elements and the dots, navigation, and sliding all adapt to the new count automatically. Put your real form fields in the steps and validate before advancing (gate the Next handler), and it becomes the wizard for any multi-step flow. It's a clear reference for sliding step navigation and single-index wizard state. Since the modal resets to step one on every open, a returning user who closes mid-flow loses their place by design here — for a setup wizard people might abandon and come back to, persist the current step (and entered values) to localStorage or your app state, and have open() restore from that instead of always resetting to zero.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Rather than tracing the carousel math yourself, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the track's transform is computed as negative step times 100 percent, and why keeping every step section mounted on the flex track (instead of toggling display per step) is what lets typed input survive moving back and forth. The same assistant can help optimize it, for instance checking whether the wizard resetting to step zero on every open is the right default versus remembering where a user left off, or whether the translateX transition duration should adapt based on how many steps are being crossed at once. It's also good for extending the modal: ask it to gate the Next button on real field validation for the "name it" and "invite team" steps, animate the progress dots with a spring easing, or persist in-progress answers to localStorage across page reloads. 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 modal wizard" in plain HTML, CSS, and JavaScript using a horizontally sliding track — no carousel library, no framework.

Requirements:
- A modal dialog (role="dialog", aria-modal) that dims the page behind a backdrop and opens with a scale-and-fade transition, opened by a trigger button and closable via clicking the backdrop, pressing Escape, or completing the flow.
- All wizard steps live side by side as full-width sections inside a flex container placed in an overflow-hidden viewport; advancing or going back must animate by transforming the flex container horizontally by exactly negative (current step index times 100 percent), not by toggling each step's display property.
- A row of progress dots, generated to match however many steps exist, where the current step's dot is visually distinct (e.g. widened into a pill), completed steps are tinted differently from upcoming ones, and this must be recomputed from a single step-index variable.
- The Back button must be disabled on the very first step. The Next/primary button's label must change based on position: a normal label mid-flow, a distinct label on the second-to-last step, and on the very last step clicking it must close the entire wizard instead of advancing further.
- Every time the modal is opened (including reopening after a previous close), it must reset back to the first step.
- The final step must be visually distinct from the rest — a centered success icon and confirmation copy rather than another input step.
- Adding or removing a step section must require zero changes to the JavaScript — the step count, dot generation, and slide math must all derive from reading the DOM at runtime.

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
    Paste HTML, CSS, and JSA button opens a wizard modal with four sliding steps and progress dots.
  2. 2
    Open the wizardClick "Set up workspace" — the modal fades in at step one.
  3. 3
    Move between stepsUse Next/Back to slide between steps; the dots and button labels update.
  4. 4
    FinishOn the last step the button reads Done and closes the wizard.
  5. 5
    Add or remove stepsAdd or delete a <section class="ms-step"> — the dots and navigation adapt automatically.
  6. 6
    Gate on validationValidate the current step's fields in the Next handler before advancing.

Real-world uses

Common Use Cases

Onboarding and setup
Guide new users through workspace setup — pair with an onboarding tour for in-app coachmarks.
Guided forms
Break a long form into focused steps inside a dialog, alongside a multi-step form for inline flows.
Account and profile setup
Collect details a step at a time with clear progress.
Feature introductions
Walk through a new feature in a few slides.
Checkout in a modal
A compact stepped checkout, next to a checkout form.
Learning wizard navigation
A reference for sliding steps and single-index state — compare with a progress wizard.
Related: Session Timeout Warning Modal
See the Session Timeout Warning Modal for a related modals pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Putting all steps on a flex track and translating it gives a smooth animated slide between steps and keeps every step in the DOM. That means any text a user typed on step two is still there if they go back from step three — toggling display:none per step also works but loses the slide animation, and recreating step DOM would risk discarding input. The carousel approach is smoother and state-preserving.

Everything derives from a single step index via update(): the track translates by -step × 100%, each dot is marked active (current), done (before current), or upcoming, Back is disabled on step zero, and Next's label changes to Finish on the second-to-last step and Done on the last (where it closes). One source of truth keeps the dots, slide, and buttons perfectly in sync.

Gate the Next handler: before incrementing step, check the current step's fields and only advance if they're valid (otherwise show an error and return). Because the steps are real DOM, you can read their inputs directly. This keeps validation per-step while the slide, dots, and controls continue to work off the step index.

Add or delete a <section class="ms-step"> in the track. The code reads the step count from the track's children, generates that many dots, and the slide math uses 100% per step — so the dots, navigation labels, and transitions all adapt to the new count with no other changes. Keep the final step as your success/confirmation screen.

Hold the step index and open state in state; render steps from an array and translate the track from the index. In React use useState plus a useEffect for the Escape listener; in Vue, refs with onMounted; in Angular, properties with HostListener. The slide math and dot logic are framework-agnostic — only the step state and listeners move into the framework.