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
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-partattribute 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
idto theuseMachinehook. 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 service = useMachine(steps.machine, { id: useId(), count: stepsData.length, }) const api = steps.connect(service, 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 service = useMachine(steps.machine, { id: createUniqueId(), count: stepsData.length, }) const api = createMemo(() => steps.connect(service, 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 service = useMachine(steps.machine, { id: "1", count: stepsData.length, }) const api = computed(() => steps.connect(service, 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
stepproperty is zero-based index.
const service = useMachine(steps.machine, { defaultStep: 1, })
Listening for step change
When the active step changes, the machine will invoke the onStepChange event
const service = 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 service = 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 service = 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 service = 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:
idsElementIdsThe custom ids for the stepper elementsstepnumberThe controlled value of the stepperdefaultStepnumberThe initial value of the stepper when rendered. Use when you don't need to control the value of the stepper.onStepChange(details: StepChangeDetails) => voidCallback to be called when the value changesonStepCompleteVoidFunctionCallback to be called when a step is completedlinearbooleanIf `true`, the stepper requires the user to complete the steps in orderorientation"horizontal" | "vertical"The orientation of the steppercountnumberThe total number of stepsdir"ltr" | "rtl"The document's text/writing direction.idstringThe unique identifier of the machine.getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The steps api exposes the following methods:
valuenumberThe value of the stepper.percentnumberThe percentage of the stepper.countnumberThe total number of steps.hasNextStepbooleanWhether the stepper has a next step.hasPrevStepbooleanWhether the stepper has a previous step.isCompletedbooleanWhether the stepper is completed.setStep(step: number) => voidFunction to set the value of the stepper.goToNextStepVoidFunctionFunction to go to the next step.goToPrevStepVoidFunctionFunction to go to the previous step.resetStepVoidFunctionFunction to go to reset the stepper.getItemState(props: ItemProps) => ItemStateReturns the state of the item at the given index.
Data Attributes
Edit this page on GitHub