preline
Version:
Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.
424 lines (331 loc) • 15.7 kB
Markdown
# Stepper
Dynamic stepper plugin that guides users through the steps of a task.
[](https://www.npmjs.com/package/@preline/stepper) [](https://opensource.org/licenses/MIT) [](https://preline.co/plugins/stepper.html)
## Contents
- [Overview](#overview)
- [Installation](#installation)
- [Basic usage](#basic-usage)
- [Configuration Options](#configuration-options)
- [JavaScript API](#javascript-api)
- [Events](#events)
- [Common Patterns](#common-patterns)
- [License](#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.
```bash
npm i @preline/stepper
```
### CSS
Use [`@source`](https://tailwindcss.com/docs/functions-and-directives#source-directive) to register the plugin's JavaScript path for Tailwind CSS scanning, then [`@import`](https://tailwindcss.com/docs/functions-and-directives#import-directive) the plugin's CSS files into your Tailwind CSS file.
```css
@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:
```html
<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.
```html
<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.
```js
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.
```js
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.
```html
<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 element
- `data-hs-stepper-nav-item`: Required on each navigation item
- `data-hs-stepper-content-item`: Required on each content panel
- `data-hs-stepper-back-btn`: Optional back button
- `data-hs-stepper-next-btn`: Optional next button
- `data-hs-stepper-finish-btn`: Optional finish button
- `data-hs-stepper-skip-btn`: Optional skip button
- `data-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)<br>`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)**
```javascript
// 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)**
```javascript
// 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**
```javascript
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**
```javascript
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
```javascript
// 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.
```html
<div data-hs-stepper='{
"mode": "non-linear"
}'>
<!-- Stepper content -->
</div>
```
### Pattern 2: Error Handling
Set error state on a step after validation fails.
```html
<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](https://opensource.org/licenses/MIT).