Overlays & Feedback

Multi Step Loader

A checklist-style loading indicator that ticks off steps one by one for long tasks.

A checklist-style loading overlay: it advances step by step, checking off the steps already completed, keeping the current step smoothly centered while the top and bottom edges fade out.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add multi-step-loader.en

New project or installation trouble? Start here →

Multi Step Loader is a React multi step loader overlay built with Tailwind CSS and Motion (motion/react). It breaks a long wait into named steps and checks them off in order, so users can see how far the process has gone.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

1500
5
Active edited usageDefault props
<MultiStepLoader/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • Setup flows that take several seconds, such as creating a workspace or importing data.
  • The wait after a form submit while the backend calls several services (validation, payment, notification email).
  • Stage copy during AI generation, like "Analyzing request, Fetching data, Writing draft".
  • An inline progress card inside an onboarding or deployment panel.
  • Not a good fit: uploads or downloads that need a real percentage; use Circular Progress instead.

Installation

npx webberui@latest add https://webberui.com/r/multi-step-loader.en.json

Or, once registries are configured in components.json, install it as @webberui/multi-step-loader.

Usage

By default the overlay fills the nearest positioned ancestor, so all you need to do is put it inside a relative container. Control visibility with the controlled loading prop.

import * as React from "react";
import { MultiStepLoader } from "@/components/ui/multi-step-loader";

const steps = ["Connecting to server", "Verifying identity", "Syncing data", "Ready"];

export function Example() {
  const [loading, setLoading] = React.useState(false);

  return (
    <div className="relative h-80 w-full">
      <button onClick={() => setLoading(true)}>Start</button>
      <MultiStepLoader
        steps={steps}
        loading={loading}
        onComplete={() => setLoading(false)}
      />
    </div>
  );
}

Each step holds for duration milliseconds before advancing. In non-looping mode, reaching the last step fires onComplete, letting you set loading back to false after you wrap up.

Examples

Wait for an async request

The component advances on a duration timer and does not know about your request; this example closes the overlay only after both the request and the step animation have finished.

import * as React from "react";
import { MultiStepLoader } from "@/components/ui/multi-step-loader";

const steps = ["Uploading file", "Parsing columns", "Checking duplicates", "Writing to database"];

export function ImportButton() {
  const [loading, setLoading] = React.useState(false);
  const request = React.useRef<Promise<unknown> | null>(null);

  function start() {
    request.current = fetch("/api/import", { method: "POST" });
    setLoading(true);
  }

  return (
    <div className="relative h-96 w-full">
      <button onClick={start}>Import contacts</button>
      <MultiStepLoader
        steps={steps}
        loading={loading}
        duration={1200}
        onComplete={async () => {
          await request.current;
          setLoading(false);
        }}
      />
    </div>
  );
}

Inline card that loops

overlay={false} renders a bordered card, and loop keeps cycling when you cannot predict how long the wait will be.

<MultiStepLoader
  steps={["Queued", "Building image", "Running tests", "Deploying to preview"]}
  loading={isDeploying}
  loop
  overlay={false}
  visibleRows={3}
  className="w-full max-w-md"
/>

Custom accent color

The check circle color comes from the --wb-loader-accent CSS variable, which you can set on a wrapper.

<div
  className="relative h-80"
  style={{ "--wb-loader-accent": "#6366f1" } as React.CSSProperties}
>
  <MultiStepLoader steps={steps} loading={loading} />
</div>

Props

PropTypeDefaultDescription
stepsstring[]—Loading copy shown step by step and checked off in order
loadingboolean—Whether the loading overlay is shown (controlled)
durationnumber1500How long each step holds (milliseconds)
loopbooleanfalseWhether to loop back to the start after the last step
visibleRowsnumber5Number of rows the visible area holds, deciding the overlay height and the top/bottom fade range
overlaybooleantruetrue fills the positioned ancestor; false renders an inline card
onComplete() => void—Fires once when the last step is reached and looping is off
classNamestring—Forwarded to the overlay container

Customization

  • Use the --wb-loader-accent CSS variable to change the accent color of the check circles; it defaults to emerald #10b981.
  • With overlay={false} the component renders as a bordered inline card, which suits placing it inside a flow panel.

Accessibility

  • The overlay carries role="status" and aria-live="polite", and the current step is announced through hidden text as "step name (n of total)".
  • The split-out list is hidden from assistive technology with aria-hidden to avoid announcing it twice.
  • When the user has "reduce motion" enabled at the system level, the centering offset and the spinner animation switch instantly instead.

FAQ

Does it work with the Next.js App Router?

Yes. The source starts with "use client", so it is a client component; import it from a Server Component page as usual. Keep the loading state in a separate client component.

Do the steps track real progress?

No. Each step holds for duration milliseconds (default 1500) and then advances; in non-looping mode onComplete fires when the last step is reached. To line it up with real work, await your request inside onComplete or tune duration to the expected time.

Why doesn't the overlay cover the area I expect?

overlay defaults to true, which uses absolute inset-0 to fill the nearest positioned ancestor, so make sure the wrapper has relative. Use overlay={false} if you do not want an overlay.

Does it handle reduced motion and screen readers?

Yes. The overlay has role="status" and aria-live="polite" and announces the current step with its position; with "reduce motion" enabled, the offset and spinner animations switch instantly.

What are the dependencies?

motion, lucide-react, clsx and tailwind-merge. You can skip the CLI and copy the source from the Manual tab.