Skip to content

useStepper

An opinionated composable for navigating through an array of steps and substeps

Initialization

In order to initialize the array that we are going to navigate using useStepper, you can do it either:

Via composable options

If you already know exactly the steps and substeps that you are going to navigate, you can simply inform them to the composable options:

ts
const steps = [
  { completed: false, substeps: [] },
  { completed: false, substeps: [] },
  { completed: false, substeps: [{ completed: false }, { completed: false }] },
  { completed: false, substeps: [] },
];

const stepper = useStepper({ steps });

Via registration method

If you don't know beforehand the steps, you can initialize the composable array as you go using the registerStep method.

This method can accept a single argument nested?: boolean indicating if it is a nested step, defaulting to false.

ts
const steps = [
  { completed: false, substeps: [] },
  { completed: false, substeps: [] },
  { completed: false, substeps: [{ completed: false }, { completed: false }] },
  { completed: false, substeps: [] },
];

const stepper = useStepper();

for (const step of steps) {
  for (const substep of step.substeps) {
    stepper.registerStep(true);
  }

  stepper.registerStep();
}

WARNING

Only one nested level is supported at the moment, meaning that substeps cannot have substeps.

Both ways yield the same result, and it can be retrieved by the readonly steps property of the composable. We can visualize them as:

Step 0
Step 1
Step 2
Substep 0
Substep 1
Step 3
template
<div class="tw-outline tw-outline-ice tw-w-full tw-p-2" v-for="(step, stepIndex) in stepper.steps.value">
  <span>Step {{ stepIndex }}</span>
  <div class="tw-flex tw-space-x-2">
    <div class="tw-outline tw-outline-ice tw-w-full tw-p-2" v-for="(substep, substepIndex) in step.substeps">
      Substep {{ substepIndex }}
    </div>
  </div>
</div>

The useStepper composable keeps track of the current active items via the activeStepIndex and activeSubstepIndex properties, which can be changed by calling back and next methods.

INFO

If next is called and the next step contains substeps, the first substep will automatically be set as active. Likewise, if back is called and the previous step contains substeps, the last substep will become active.

In order to check whether a step (or substep) is active, the composable exposes the isStepActive method, accepting a step index, and an optional substep index.

Step 0
Step 1
Step 2
Substep 0
Substep 1
Step 3
template
<div class="tw-flex tw-justify-between tw-space-x-2">
  <div
    class="tw-w-full tw-p-2 tw-outline"
     v-for="(step, stepIndex) in stepper.steps.value" :class="[stepper.isStepActive(stepIndex) ? 'tw-outline-blue' : 'tw-outline-ice']">
    <span>Step {{ stepIndex }}</span>
    <div class="tw-flex tw-space-x-2">
      <div class="tw-w-full tw-p-2 tw-outline" v-for="(substep, substepIndex) in step.substeps" :class="[stepper.isStepActive(stepIndex, substepIndex) ? 'tw-outline-blue' : 'tw-outline-ice']">
        Substep {{ substepIndex }}
      </div>
    </div>
  </div>
</div>
<div class="tw-w-full tw-flex tw-justify-between tw-mt-2">
  <Button @click="stepper.back" :disabled="stepper.activeStepIndex.value <= 0">back</Button>
  <Button @click="stepper.next">next</Button>
</div>

Alternatively, you can allso call the goTo method that can be used to navigate to a specific step or substep programmatically.

TIP

Try clicking on steps or substeps boxes bellow

Step 0
Step 1
Step 2
Substep 0
Substep 1
Step 3
template
  <div class="tw-flex tw-justify-between tw-space-x-2">
  <div
    class="tw-w-full tw-p-2 tw-outline tw-cursor-pointer"
    v-for="(step, stepIndex) in stepper.steps.value"
    :class="[stepper.isStepActive(stepIndex) ? 'tw-outline-blue' : 'tw-outline-ice']"
    @click="stepper.goTo(stepIndex)"
  >
      <span>Step {{ stepIndex }}</span>
      <div class="tw-flex tw-space-x-2">
      <div
        class="tw-w-full tw-p-2 tw-outline"
        v-for="(substep, substepIndex) in step.substeps"
        :class="[stepper.isStepActive(stepIndex, substepIndex) ? 'tw-outline-blue' : 'tw-outline-ice']"
        @click.stop="stepper.goTo(stepIndex, substepIndex)"
      >
          <span>Substep {{ substepIndex }}</span>
      </div>
    </div>
  </div>
</div>

INFO

The useStepper composable also keeps track of the current active step element based on the activeStepIndex if a stepper ref is provided. The element is accessible using the activeStepElement property.

Completion

The useStepper composable also keeps track of the steps that are completed or not. A step is considered completed when the next method is called.

INFO

A step that contains substeps is only considered completed if all the substeps are completed.

In order to check whether a step (or substep) is completed, the composable exposes the isStepCompleted method, accepting a step index, and an optional substep index.

Step 0
Step 1
Step 2
Substep 0
Substep 1
Step 3
template
<div class="tw-flex tw-justify-between tw-space-x-2">
  <div
    class="tw-w-full tw-p-2 tw-outline tw-cursor-pointer"
    v-for="(step, stepIndex) in completionStepper.steps.value"
    :class="[completionStepper.isStepActive(stepIndex) ? 'tw-outline-blue' : completionStepper.isStepCompleted(stepIndex) ? 'tw-outline-green' : 'tw-outline-ice']"
    @click="completionStepper.goTo(stepIndex)"
  >
      <span>Step {{ stepIndex }}</span>
      <div class="tw-flex tw-space-x-2">
      <div
        class="tw-w-full tw-p-2 tw-outline"
        v-for="(substep, substepIndex) in step.substeps"
        :class="[completionStepper.isStepActive(stepIndex, substepIndex) ? 'tw-outline-blue' : completionStepper.isStepCompleted(stepIndex, substepIndex) ? 'tw-outline-green' : 'tw-outline-ice']"
        @click.stop="completionStepper.goTo(stepIndex, substepIndex)"
      >
          <span>Substep {{ substepIndex }}</span>
      </div>
    </div>
  </div>
</div>
<div class="tw-w-full tw-flex tw-justify-between tw-mt-2">
  <Button @click="completionStepper.back" :disabled="completionStepper.activeStepIndex.value <= 0">back</Button>
  <Button @click="completionStepper.next">next</Button>
</div>

Linearity

By default, the useStepper is initialized as being non-linear, meaning that you can go to any step that you want, at any given order.

If you want to ensure that the user can only navigate to the immediate step after the last completed one, you can set the linear config option to true.

TIP

Try clicking on a step that is not the first in the example below

Step 0
Step 1
Step 2
Substep 0
Substep 1
Step 3
template
  <div class="tw-flex tw-justify-between tw-space-x-2">
  <div
    class="tw-w-full tw-p-2 tw-outline tw-cursor-pointer"
    v-for="(step, stepIndex) in linearStepper.steps.value"
    :class="[linearStepper.isStepActive(stepIndex) ? 'tw-outline-blue' : linearStepper.isStepCompleted(stepIndex) ? 'tw-outline-green' : 'tw-outline-ice']"
    @click="linearStepper.goTo(stepIndex)"
  >
      <span>Step {{ stepIndex }}</span>
      <div class="tw-flex tw-space-x-2">
      <div
        class="tw-w-full tw-p-2 tw-outline"
        v-for="(substep, substepIndex) in step.substeps"
        :class="[linearStepper.isStepActive(stepIndex, substepIndex) ? 'tw-outline-blue' : linearStepper.isStepCompleted(stepIndex, substepIndex) ? 'tw-outline-green' : 'tw-outline-ice']"
        @click.stop="linearStepper.goTo(stepIndex, substepIndex)"
      >
          <span>Substep {{ substepIndex }}</span>
      </div>
    </div>
  </div>
</div>
<div class="tw-w-full tw-flex tw-justify-between tw-mt-2">
  <Button @click="linearStepper.back" :disabled="linearStepper.activeStepIndex.value <= 0">back</Button>
  <Button @click="linearStepper.next">next</Button>
</div>