Skip to content
Destyler Destyler Destyler

Steps

Step 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 step.

Step 1
Step 1

Features

Install

Install the component from your command line.

Terminal window
      
        
npm install @destyler/steps @destyler/vue
Terminal window
      
        
npm install @destyler/steps @destyler/react
Terminal window
      
        
npm install @destyler/steps @destyler/svelte
Terminal window
      
        
npm install @destyler/steps @destyler/solid

Anatomy

Import all parts and piece them together.

<script setup lang="ts">
import * as steps from '@destyler/steps'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, useId } from 'vue'
const stepsData = [
{ title: 'Step 1' },
{ title: 'Step 2' },
{ title: 'Step 3' },
]
const [state, send] = useMachine(
steps.machine({
id: useId(),
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 })"></div>
</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 }}
</div>
<div v-bind="api.getContentProps({ index: stepsData.length })"></div>
<button v-bind="api.getPrevTriggerProps()"></button>
<button v-bind="api.getNextTriggerProps()"></button>
</div>
</template>
import { normalizeProps, useMachine } from '@destyler/react'
import * as steps from '@destyler/steps'
import { useId } from 'react'
const stepsData = [
{ title: 'Step 1' },
{ title: 'Step 2' },
{ title: 'Step 3' },
]
export default 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 })}></div>
</button>
<div {...api.getSeparatorProps({ index })}/>
</div>
))}
</div>
{stepsData.map((step, index) => (
<div key={index} {...api.getContentProps({ index })}></div>
))}
<div {...api.getContentProps({ index: stepsData.length })}></div>
<button {...api.getPrevTriggerProps()}></button>
<button {...api.getNextTriggerProps()}></button>
</div>
)
}
<script lang="ts">
import * as steps from '@destyler/steps'
import { normalizeProps, useMachine } from '@destyler/svelte'
const stepsData = [
{ title: 'Step 1' },
{ title: 'Step 2' },
{ title: 'Step 3' },
]
const [state, send] = useMachine(
steps.machine({
id: crypto.randomUUID(),
count: stepsData.length,
}),
)
const api = $derived(steps.connect(state, send, normalizeProps))
</script>
<div {...api.getRootProps()}>
<div {...api.getListProps()}>
{#each stepsData as step, index}
<div {...api.getItemProps({ index })}>
<button {...api.getTriggerProps({ index })}>
<div {...api.getIndicatorProps({ index })} ></div>
</button>
<div {...api.getSeparatorProps({ index })}></div>
</div>
{/each}
</div>
{#each stepsData as step, index}
<div {...api.getContentProps({ index })}></div>
{/each}
<div {...api.getContentProps({ index: stepsData.length })}></div>
<button {...api.getPrevTriggerProps()}></button>
<button {...api.getNextTriggerProps()}></button>
</div>
import { normalizeProps, useMachine } from '@destyler/solid'
import * as steps from '@destyler/steps'
import { createMemo, createUniqueId } from 'solid-js'
const stepsData = [
{ title: 'Step 1' },
{ title: 'Step 2' },
{ title: 'Step 3' },
]
export default 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()} >
{stepsData.map((step, index) => (
<div {...api().getItemProps({ index })}>
<button {...api().getTriggerProps({ index })} >
<div {...api().getIndicatorProps({ index })}></div>
</button>
<div {...api().getSeparatorProps({ index })}/>
</div>
))}
</div>
{stepsData.map((step, index) => (
<div {...api().getContentProps({ index })}></div>
))}
<div
{...api().getContentProps({ index: stepsData.length })}
>
</div>
<button {...api().getPrevTriggerProps()}></button>
<button {...api().getNextTriggerProps()}></button>
</div>
)
}

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({
defaultStep: 1,
}),
)

Controlled usage

Steps step index can be uncontrolled or controlled. Prefer defaultStep for an uncontrolled seed. Passing step into machine(...) is presence-controlled — sync with onStepChange and adapter context. See Controllable State.

steps.machine({
id: "1",
count: 3,
defaultStep: 0,
})
const step = 0 // your reactive state
steps.machine({
id: "1",
count: 3,
step,
onStepChange(details) {
// details => { step: number }
},
})

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(`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 splitter 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"] {
/* 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"] {
/* 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"] {
/* 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 elements
step
number
The controlled current step index
defaultStep
number
The initial step index when it is not controlled. Prefer this over seeding with `step` for uncontrolled usage.
onStepChange
(details: StepChangeDetails) => void
Callback to be called when the value changes
onStepComplete
VoidFunction
Callback to be called when a step is completed
linear
boolean
If `true`, the stepper requires the user to complete the steps in order
orientation
"horizontal" | "vertical"
The orientation of the stepper
count
number
The total number of steps
dir(default: "ltr")
"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.
isCompleted
boolean
Whether the stepper is completed.
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

Root

attribute
description
data-scope
steps
data-part
root
data-orientation
The orientation of the steps

List

attribute
description
data-scope
steps
data-part
list
data-orientation
The orientation of the list

Item

attribute
description
data-scope
steps
data-part
item
data-orientation
The orientation of the item

Trigger

attribute
description
data-scope
steps
data-part
trigger
data-state
"open" | "closed"
data-orientation
The orientation of the trigger
data-complete
Present when the trigger value is complete
data-current
Present when current
data-incomplete

Content

attribute
description
data-scope
steps
data-part
content
data-state
"open" | "closed"
data-orientation
The orientation of the content

Indicator

attribute
description
data-scope
steps
data-part
indicator
data-complete
Present when the indicator value is complete
data-current
Present when current
data-incomplete

Separator

attribute
description
data-scope
steps
data-part
separator
data-orientation
The orientation of the separator
data-complete
Present when the separator value is complete
data-current
Present when current
data-incomplete

Progress

attribute
description
data-scope
steps
data-part
progress
data-complete
Present when the progress value is complete