Steps
Steps are used to guide users through a series of steps in a process. It's a great way to break down a complex process into smaller, more manageable steps.
Features
- Supports horizontal and vertical orientations.
- Support for changing the active step with the keyboard and pointer.
- Support for linear and non-linear steps.
Installation
To use the steps machine in your project, run the following command in your command line:
npm install @zag-js/steps @zag-js/react # or yarn add @zag-js/steps @zag-js/react
npm install @zag-js/steps @zag-js/solid # or yarn add @zag-js/steps @zag-js/solid
npm install @zag-js/steps @zag-js/vue # or yarn add @zag-js/steps @zag-js/vue
This command will install the framework agnostic steps logic and the reactive utilities for your framework of choice.
Anatomy
To set up the steps correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
Usage
First, import the steps package into your project
import * as steps from "@zag-js/steps"
The steps package exports two key functions:
machine
— The state machine logic for the steps widget.connect
— The function that translates the machine's state to JSX attributes and event handlers.
You'll also need to provide a unique
id
to theuseMachine
hook. This is used to ensure that every part has a unique identifier.
Next, import the required hooks and functions for your framework and use the steps machine in your project 🔥
import * as steps from "@zag-js/steps" import { useMachine, normalizeProps } from "@zag-js/react" import { useId } from "react" const stepsData = [ { title: "Step 1" }, { title: "Step 2" }, { title: "Step 3" }, ] function Steps() { const [state, send] = useMachine( steps.machine({ id: useId(), count: stepsData.length }), ) const api = steps.connect(state, send, normalizeProps) return ( <div {...api.getRootProps()}> <div {...api.getListProps()}> {stepsData.map((step, index) => ( <div key={index} {...api.getItemProps({ index })}> <button {...api.getTriggerProps({ index })}> <div {...api.getIndicatorProps({ index })}>{index + 1}</div> <span>{step.title}</span> </button> <div {...api.getSeparatorProps({ index })} /> </div> ))} </div> {stepsData.map((step, index) => ( <div key={index} {...api.getContentProps({ index })}> {step.title} </div> ))} <div {...api.getContentProps({ index: stepsData.length })}> Steps Complete - Thank you for filling out the form! </div> <div> <button {...api.getPrevTriggerProps()}>Back</button> <button {...api.getNextTriggerProps()}>Next</button> </div> </div> ) }
import * as steps from "@zag-js/steps" import { useMachine, normalizeProps } from "@zag-js/solid" import { createMemo, createUniqueId } from "solid-js" const stepsData = [ { title: "Step 1" }, { title: "Step 2" }, { title: "Step 3" }, ] function Steps() { const [state, send] = useMachine( steps.machine({ id: createUniqueId(), count: stepsData.length, }), ) const api = createMemo(() => steps.connect(state, send, normalizeProps)) return ( <div {...api().getRootProps()}> <div {...api().getListProps()}> <Index each={stepsData}> {(step, index) => ( <div {...api().getItemProps({ index })}> <button {...api().getTriggerProps({ index })}> <div {...api().getIndicatorProps({ index })}>{index + 1}</div> <span>{step().title}</span> </button> <div {...api().getSeparatorProps({ index })} /> </div> )} </Index> </div> <Index each={stepsData}> {(step, index) => ( <div {...api().getContentProps({ index })}> {step().title} - {step().description} </div> )} </Index> <div {...api().getContentProps({ index: stepsData.length })}> Steps Complete - Thank you for filling out the form! </div> <div> <button {...api().getPrevTriggerProps()}>Back</button> <button {...api().getNextTriggerProps()}>Next</button> </div> </div> ) }
<script setup> import * as steps from "@zag-js/steps" import { useMachine, normalizeProps } from "@zag-js/vue" import { computed } from "vue" const stepsData = [ { title: "Step 1" }, { title: "Step 2" }, { title: "Step 3" }, ] const [state, send] = useMachine( steps.machine({ id: "1", count: stepsData.length, }), ) const api = computed(() => steps.connect(state.value, send, normalizeProps)) </script> <template> <div v-bind="api.getRootProps()"> <div v-bind="api.getListProps()"> <div v-for="(step, index) in stepsData" :key="index" v-bind="api.getItemProps({ index })" > <button v-bind="api.getTriggerProps({ index })"> <div v-bind="api.getIndicatorProps({ index })">{{ index + 1 }}</div> <span>{{ step.title }}</span> </button> <div v-bind="api.getSeparatorProps({ index })" /> </div> </div> <div v-for="(step, index) in stepsData" :key="index" v-bind="api.getContentProps({ index })" > {{ step.title }} - {{ step.description }} </div> <div v-bind="api.getContentProps({ index: stepsData.length })"> Steps Complete - Thank you for filling out the form! </div> <div> <button v-bind="api.getPrevTriggerProps()">Back</button> <button v-bind="api.getNextTriggerProps()">Next</button> </div> </div> </template>
Setting the initial step
Set the initial step by passing the step
property to the machine context.
The value of the
step
property is zero-based index.
const [state, send] = useMachine( steps.machine({ step: 1, }), )
Listening for step change
When the active step changes, the machine will invoke the onStepChange
event
const [state, send] = useMachine( steps.machine({ onStepChange(details) { // details => { step: number } console.log(`Step changed to ${details.step}`) }, }), )
Listening for steps completion
When all steps are completed, the machine will invoke the onStepComplete
event
const [state, send] = useMachine( steps.machine({ onStepComplete() { console.log("All steps are complete") }, }), )
Enforcing linear steps
To enforce linear steps, you can set the linear
prop to true
when creating
the steps machine. This will prevent users from skipping steps.
const [state, send] = useMachine( steps.machine({ linear: true, }), )
Changing the orientation
The steps machine supports both horizontal and vertical orientations. You can
set the orientation
prop to horizontal
or vertical
to change the
orientation of the steps.
const [state, send] = useMachine( steps.machine({ orientation: "vertical", }), )
Styling guide
Earlier, we mentioned that each steps part has a data-part
attribute added to
them to select and style them in the DOM.
[data-scope="steps"][data-part="root"] { /* styles for the root part */ } [data-scope="steps"][data-part="root"][data-orientation="horizontal|vertical"] { /* styles for the root part based on orientation */ } [data-scope="steps"][data-part="list"] { /* styles for the list part */ } [data-scope="steps"][data-part="list"][data-orientation="horizontal|vertical"] { /* styles for the list part based on orientation */ } [data-scope="steps"][data-part="separator"] { /* styles for the separator part */ } [data-scope="steps"][data-part="separator"][data-orientation="horizontal|vertical"] { /* styles for the separator part based on orientation */ }
Current step
To style the current step, you can use the data-current
attribute.
[data-scope="steps"][data-part="item"][data-current] { /* item styles for the current step */ } [data-scope="steps"][data-part="separator"][data-current] { /* separator styles for the current step */ }
Completed step
To style the completed step, you can use the data-complete
attribute.
[data-scope="steps"][data-part="item"][data-complete] { /* item styles for the completed step */ } [data-scope="steps"][data-part="separator"][data-complete] { /* separator styles for the completed step */ }
Incomplete step
To style the incomplete step, you can use the data-incomplete
attribute.
[data-scope="steps"][data-part="item"][data-incomplete] { /* item styles for the incomplete step */ } [data-scope="steps"][data-part="separator"][data-incomplete] { /* separator styles for the incomplete step */ }
Methods and Properties
Machine Context
The steps machine exposes the following context properties:
ids
ElementIds
The custom ids for the stepper elementsstep
number
The current value of the stepperonStepChange
(details: StepChangeDetails) => void
Callback to be called when the value changesonStepComplete
VoidFunction
Callback to be called when a step is completedlinear
boolean
If `true`, the stepper requires the user to complete the steps in orderorientation
"horizontal" | "vertical"
The orientation of the steppercount
number
The total number of stepsdir
"ltr" | "rtl"
The document's text/writing direction.id
string
The unique identifier of the machine.getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The steps api
exposes the following methods:
value
number
The value of the stepper.percent
number
The percentage of the stepper.count
number
The total number of steps.hasNextStep
boolean
Whether the stepper has a next step.hasPrevStep
boolean
Whether the stepper has a previous step.setStep
(step: number) => void
Function to set the value of the stepper.goToNextStep
() => void
Function to go to the next step.goToPrevStep
() => void
Function to go to the previous step.resetStep
() => void
Function to go to reset the stepper.getItemState
(props: ItemProps) => ItemState
Returns the state of the item at the given index.
Data Attributes
Edit this page on GitHub