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.enMulti 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.
<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.jsonOr, 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
| Prop | Type | Default | Description |
|---|---|---|---|
steps | string[] | — | Loading copy shown step by step and checked off in order |
loading | boolean | — | Whether the loading overlay is shown (controlled) |
duration | number | 1500 | How long each step holds (milliseconds) |
loop | boolean | false | Whether to loop back to the start after the last step |
visibleRows | number | 5 | Number of rows the visible area holds, deciding the overlay height and the top/bottom fade range |
overlay | boolean | true | true fills the positioned ancestor; false renders an inline card |
onComplete | () => void | — | Fires once when the last step is reached and looping is off |
className | string | — | Forwarded to the overlay container |
Customization
- Use the
--wb-loader-accentCSS 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"andaria-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-hiddento 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.