Stepper
Dynamic stepper plugin that guides users through the steps of a task.
Contents
- Overview
- Installation
- Basic usage
- Configuration Options
- JavaScript API
- Events
- Common Patterns
- License
Overview
The Stepper component provides a multi-step wizard interface that guides users through a sequential process. It supports both linear (sequential) and non-linear (jumpable) modes, with options for optional steps, error states, and completion tracking.
Key Features:
- Multi-step wizard interface
- Linear and non-linear navigation modes
- Optional steps support
- Error and processing states
- Completion tracking
- Programmatic control via JavaScript API
- Event system for step transitions
- Accessibility attributes (ARIA) built-in
Installation
To get started, install Stepper plugin via npm, else you can skip this step if you are already using Preline UI as a package.
npm i @preline/stepper
CSS
Use @source to register the plugin's JavaScript path for Tailwind CSS scanning, then @import the plugin's CSS files into your Tailwind CSS file.
@import "tailwindcss";
/* @preline/stepper */
@source "../node_modules/@preline/stepper/*.js";
@import "./node_modules/@preline/stepper/variants.css";
@import "./node_modules/@preline/stepper/theme.css";
JavaScript
Include the JavaScript that powers the interactive elements near the end of your </body> tag:
<script src="./node_modules/@preline/stepper/index.js"></script>
Manual Initialization
Use the non-auto entry if you need manual initialization. In this mode, automatic initialization on page load is not included, so the component should be initialized explicitly.
<script type="module">
import HSStepper from "@preline/stepper/non-auto.mjs";
new HSStepper(document.querySelector("#stepper"));
</script>
Via Bundler
When using a bundler (Vite, webpack, etc.), import the plugin directly as an ES module.
@preline/stepper is the auto-init entry: it scans the DOM and initializes matching elements automatically.
import "@preline/stepper";
@preline/stepper/non-auto is the manual entry: use it when you want explicit control over when initialization happens, either via autoInit() or by creating a specific instance yourself.
import HSStepper from "@preline/stepper/non-auto";
HSStepper.autoInit();
// Or initialize a specific element manually
const el = document.querySelector("#stepper");
if (el) new HSStepper(el);
TypeScript
This package ships with TypeScript type definitions. No additional @types/ package is needed.
Basic usage
The following example demonstrates the minimal HTML structure required for a stepper component. This is a base template without custom styling - you can apply your own CSS classes and styles as needed. The example shows three steps with navigation buttons.
<div data-hs-stepper>
<div class="hs-stepper-active:text-blue-500 hs-stepper-success:text-blue-500" data-hs-stepper-nav-item='{
"index": 1
}'>
1 Step
</div>
<div class="hs-stepper-active:text-blue-500 hs-stepper-success:text-blue-500" data-hs-stepper-nav-item='{
"index": 2
}'>
2 Step
</div>
<div class="hs-stepper-active:text-blue-500 hs-stepper-success:text-blue-500" data-hs-stepper-nav-item='{
"index": 3
}'>
3 Step
</div>
<div data-hs-stepper-content-item='{
"index": 1
}' style="display: none;">
First content.
</div>
<div data-hs-stepper-content-item='{
"index": 2
}' style="display: none;">
Second content.
</div>
<div data-hs-stepper-content-item='{
"index": 3
}' style="display: none;">
Third content.
</div>
<div data-hs-stepper-content-item='{
"isFinal": true
}' style="display: none;">
Final content.
</div>
<button class="hs-stepper-disabled:opacity-50" type="button" data-hs-stepper-back-btn>
Back
</button>
<button type="button" data-hs-stepper-skip-btn style="display: none;">
Skip
</button>
<button type="button" data-hs-stepper-next-btn>
Next
</button>
<button type="button" data-hs-stepper-finish-btn style="display: none;">
Finish
</button>
<button type="reset" data-hs-stepper-reset-btn style="display: none;">
Reset
</button>
</div>
Structure Requirements:
data-hs-stepper: Required on the container elementdata-hs-stepper-nav-item: Required on each navigation itemdata-hs-stepper-content-item: Required on each content paneldata-hs-stepper-back-btn: Optional back buttondata-hs-stepper-next-btn: Optional next buttondata-hs-stepper-finish-btn: Optional finish buttondata-hs-stepper-skip-btn: Optional skip buttondata-hs-stepper-reset-btn: Optional reset button
Initial State:
- First step is active by default (index 1)
- Content items should have
style="display: none;"except the first one - Navigation buttons visibility is controlled by the plugin
Configuration Options
Data Options
Data options are specified in the data-hs-stepper, data-hs-stepper-nav-item, and data-hs-stepper-content-item attributes.
| Attribute | Target Element | Type | Default | Description |
|---|---|---|---|---|
data-hs-stepper |
Container | - | - | Activate a Stepper by specifying on an element. Should be added to the container. |
:currentIndex |
Inside data-hs-stepper |
number | 1 |
Index of the current step. This must be a number between 1 and the maximum number of steps. |
:mode |
Inside data-hs-stepper |
"linear" | "non-linear" |
"linear" |
The mode of the stepper. In "non-linear" mode, the user can navigate to any step and nav items are clickable. In "linear" mode, the user can only navigate to the next/back step. |
:isCompleted |
Inside data-hs-stepper |
boolean | false |
Whether the stepper is completed. |
data-hs-stepper-nav-item |
Navigation item | - | - | Activate a Stepper Nav Item by specifying on an element. Should be added to the nav item. |
:index |
Inside data-hs-stepper-nav-item |
number | - | The index of the step to which the item belongs. This must be a number between 1 and the maximum number of steps. |
:isOptional |
Inside data-hs-stepper-nav-item |
boolean | false |
Whether the step is optional. |
:isCompleted |
Inside data-hs-stepper-nav-item |
boolean | false |
Whether the step is completed. |
:isSkip |
Inside data-hs-stepper-nav-item |
boolean | false |
Whether the step is skipped. |
:hasError |
Inside data-hs-stepper-nav-item |
boolean | false |
Whether the step has an error. |
data-hs-stepper-content-item |
Content item | - | - | Activate a Stepper Content Item by specifying on an element. Should be added to the content item. |
:index |
Inside data-hs-stepper-content-item |
number | - | The index of the step to which the item belongs. This must be a number between 1 and the maximum number of steps. |
:isCompleted |
Inside data-hs-stepper-content-item |
boolean | false |
Whether the step is completed. |
:isSkip |
Inside data-hs-stepper-content-item |
boolean | false |
Whether the step is skipped. |
:isFinal |
Inside data-hs-stepper-content-item |
boolean | false |
Whether the step is final. |
Tailwind Modifiers
| Name | Description |
|---|---|
hs-stepper-active:* |
Modifies the active step. |
hs-stepper-success:* |
Modifies the completed step. |
hs-stepper-disabled:* |
Modifies the "back" button when the very first step is active. |
hs-stepper-skipped:* |
Modifies the skipped step. |
hs-stepper-error:* |
Modifies the step that has error. Error class should be added manually by some event. E.g. after form validation. |
hs-stepper-process:* |
Modifies the step that processing. Process class should be added manually by some event. E.g. after form submit. |
hs-stepper-completed:* |
Modifies all steps are completed. |
JavaScript API
The HSStepper object is available in the global window object after the plugin is loaded.
Instance Methods
These methods are called on a stepper instance.
| Method | Parameters | Return Type | Description |
|---|---|---|---|
setProcessedNavItem(n) |
n: number |
void |
Set the nav item as processed. n is the index of the step to which the item belongs. |
setErrorNavItem(n) |
n: number |
void |
Set the nav item as error. n is the index of the step to which the item belongs. |
unsetProcessedNavItem(n) |
n: number |
void |
Unset the nav item as processed. n is the index of the step to which the item belongs. |
goToNext() |
None | number |
Go to the next step. Returns the index of the next step. If the current step is the last, returns the index of the current step. |
goToFinish() |
None | number |
Go to the finish step. Returns the index of the finish step. If the current step is the last, returns the index of the current step. |
disableButtons() |
None | void |
Disable the "next" and "back" buttons. |
enableButtons() |
None | void |
Enable the "next" and "back" buttons. |
destroy() |
None | void |
Destroys the stepper instance, removes all generated markup, classes, and event listeners. Use when removing stepper from DOM. |
Static Methods
These methods are called directly on the HSStepper class.
| Method | Parameters | Return Type | Description |
|---|---|---|---|
HSStepper.getInstance(target, isInstance) |
target: HTMLElement | string (CSS selector)isInstance: boolean (optional) |
HSStepper | { id: string | number, element: HSStepper } | null |
Returns the stepper instance associated with the target. If isInstance is true, returns collection item object { id, element } where element is the HSStepper instance. If isInstance is false or omitted, returns the HSStepper instance directly. Returns null if stepper instance is not found. |
Usage Examples
Example 1: Using instance methods (public API)
// Create a new stepper instance
const stepper = new HSStepper(document.querySelector('#hs-stepper'));
let errorState = 1;
stepper.on('beforeNext', (index) => {
if (index === 2) {
stepper.setProcessedNavItem(index);
setTimeout(() => {
stepper.unsetProcessedNavItem(index);
stepper.enableButtons();
if (errorState) {
stepper.goToNext();
} else {
stepper.setErrorNavItem(index);
}
errorState = !errorState;
}, 2000);
}
});
Example 2: Getting instance and using methods (recommended pattern)
// Get the stepper instance
const instance = HSStepper.getInstance('#hs-stepper', true);
if (instance) {
const { element } = instance; // element is the HSStepper instance
let errorState = 1;
element.on('beforeNext', (index) => {
if (index === 2) {
element.setProcessedNavItem(index);
setTimeout(() => {
element.unsetProcessedNavItem(index);
element.enableButtons();
if (errorState) {
element.goToNext();
} else {
element.setErrorNavItem(index);
}
errorState = !errorState;
}, 2000);
}
});
// Clean up when removing from DOM
function removeStepper() {
element.destroy();
}
}
Example 3: Programmatic navigation
const instance = HSStepper.getInstance('#hs-stepper', true);
if (instance) {
const { element } = instance;
// Navigate to next step
const nextIndex = element.goToNext();
console.log('Moved to step:', nextIndex);
// Navigate to finish
const finishIndex = element.goToFinish();
console.log('Finished at step:', finishIndex);
}
Example 4: Destroying stepper instance
const instance = HSStepper.getInstance('#hs-stepper', true);
if (instance) {
const { element } = instance;
const removeBtn = document.querySelector('#hs-remove-btn');
removeBtn.addEventListener('click', () => {
// Clean up before removing from DOM
element.destroy();
// Now safe to remove the element
document.querySelector('#hs-stepper').remove();
});
}
Events
Stepper instances emit events that can be listened to for step transitions and custom behavior.
| Event Name | When Fired | Callback Parameter | Description |
|---|---|---|---|
on:active |
When the "active" class is set up | currentIndex (number) |
Fires when a step becomes active. |
on:beforeNext |
Before the "next" button is clicked | currentIndex (number) |
Fires before navigating to the next step. Can be used to validate or prevent navigation. |
on:beforeFinish |
Before the "finish" button is clicked | currentIndex (number) |
Fires before finishing the stepper. Can be used to validate or prevent completion. |
on:next |
When the "next" button is clicked | currentIndex (number) |
Fires when navigating to the next step. |
on:back |
When the "back" button is clicked | currentIndex (number) |
Fires when navigating to the previous step. |
on:complete |
When the "complete" button is clicked | currentIndex (number) |
Fires when completing a step. |
on:finish |
When the "finish" button is clicked | currentIndex (number) |
Fires when finishing the stepper. |
Event Usage Example
// Get stepper instance
const instance = HSStepper.getInstance('#hs-stepper', true);
if (instance) {
const { element } = instance;
// Listen to step activation
element.on('active', (currentIndex) => {
console.log('Step activated:', currentIndex);
// Perform actions when step becomes active
// e.g., load content, initialize form fields
});
// Listen to before next (for validation)
element.on('beforeNext', (currentIndex) => {
console.log('Before next step:', currentIndex);
// Validate current step before proceeding
// Can prevent navigation if validation fails
});
// Listen to finish
element.on('finish', (currentIndex) => {
console.log('Stepper finished:', currentIndex);
// Perform final actions
// e.g., submit form, show success message
});
}
Common Patterns
Pattern 1: Non-linear Mode
Allow users to jump to any step.
<div data-hs-stepper='{
"mode": "non-linear"
}'>
<!-- Stepper content -->
</div>
Pattern 2: Error Handling
Set error state on a step after validation fails.
<div id="hs-stepper-first" data-hs-stepper>
<!-- Stepper content -->
</div>
<script>
const instance = HSStepper.getInstance('#hs-stepper-first', true);
if (instance) {
const { element } = instance;
// Validate and set error if needed
function validateStep(stepIndex) {
// Validation logic
if (validationFails) {
element.setErrorNavItem(stepIndex);
}
}
}
</script>
License
Copyright (c) 2024 Htmlstream.
Licensed under the MIT License.