@preline/stepper
v4.2.0
Published
Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.
Readme
Stepper
Dynamic stepper plugin that guides users through the steps of a task.
Contents
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/stepperCSS
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) 2026 Preline Labs.
Licensed under the MIT License.
