Stepperize v8Explore the changes
Stepperize

Status vs completion

Understand how step status and completion differ.

This is the concept that trips people up most, so let's make it concrete. A step has two unrelated states, and Stepperize tracks them separately.

Move the active step (click a circle) and toggle completion (the badges) independently. Notice they never affect each other.

Two independent axes

Click a step to move. Click a completion cell to toggle it. Only one row follows the position.

Status and completion for each step
Axis
status(id)derived from positionpreviousactiveupcomingupcoming
isComplete(id)set by you

Status is positional. Move the active step and every cell in the row recolors, with no extra calls.

Completion is business state. A previous step is not automatically complete, and moving never clears it.

Two axes, never linked

StatusCompletion
What it isWhere the step sits relative to the active oneWhether your app considers it done
Who sets itStepperize, automaticallyYou, explicitly
Values"previous" · "active" · "upcoming"true / false
Read withstepper.status(id)stepper.isComplete(id)
Write withnavigation (next, goTo, …)setComplete(id) / setComplete(id, false)

The key insight: a "previous" step is not automatically completed. The user walking past a step says nothing about whether that step is valid, submitted, or approved. Only your app knows that.

Why the split exists

Real flows need both:

  • Status drives visual position — highlight the current step, dim upcoming ones, draw a line behind finished ones. It's UI sugar, derived for free.
  • Completion drives business rules — enable "Submit" only when every step is complete, show a green check after a step is validated, let users jump back to "completed" steps but not "upcoming" ones.
// Positional: style the step list
<Stepper.Item /* data-status="active | previous | upcoming" */ />

// Business: gate the final action
const allDone = stepper.steps.every((s) => stepper.isComplete(s.id));
<button disabled={!allDone}>Place order</button>

Common pattern: complete on a valid "next"

Mark a step complete only once it passes its guard:

await stepper.next({ data: formValues, complete: true });

That keeps completion meaningful: a step is "done" because it was accepted, not merely because the user scrolled past it.

Next: controlled vs uncontrolled.

Edit on GitHub

Last updated on

On this page