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:
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.
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:
<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>Navigation
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.
<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
<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.
<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
<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>