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>