Skip to content

Stepper

A multi-step progress indicator, built on Reka UI's Stepper primitive.

Usage

Stepper only tracks and displays which step is current - v-model is a plain 1-indexed number, and pairing it with your own step content and Back/Next buttons is up to you, the same way v-model composes with anything else:

Cart

Shipping

Payment

Step 1 of 3

Step 1: Cart

vue
<script setup lang="ts">
const items = [{ title: 'Cart' }, { title: 'Shipping' }, { title: 'Payment' }]
const step = ref(1)
</script>

<template>
  <SStepper v-model="step" :items="items" />
  <SButton :disabled="step === 1" @click="step--">
    Back
  </SButton>
  <SButton :disabled="step === items.length" @click="step++">
    Next
  </SButton>
</template>

By default, clicking a step only ever advances at most one step ahead of the current one - going back to any earlier (completed) step is always allowed. Pass linear="false" to allow jumping to any step directly:

One

Two

Three

Four

Step 1 of 4
vue-html
<SStepper :items="items" :linear="false" />

Descriptions

Each item can carry a description alongside its title:

Address

Where to ship

Delivery

Pick a speed

Review

Confirm order

Step 2 of 3
vue-html
<SStepper :items="[{ title: 'Address', description: 'Where to ship' }, ...]" />

Icons

Each item can carry an icon, shown once its step is active or upcoming

  • a completed step always shows a checkmark instead, regardless of icon:

Account

Preferences

Billing

Step 2 of 3
vue-html
<SStepper :items="[{ title: 'Account', icon: 'hugeicons:user' }, ...]" />

Custom slots

#indicator, #title, and #description each replace that part entirely, scoped with the item, its index, and its state - useful for always showing an item's own icon (instead of falling back to a checkmark once completed) or annotating the active step:

Draft

Review · current

Published

Step 2 of 3
vue-html
<SStepper :items="items">
  <template #indicator="{ item }">
    <SIcon :name="item.icon" class="size-4" />
  </template>
  <template #title="{ item, state }">
    {{ item.title }}
    <span v-if="state === 'active'">· current</span>
  </template>
</SStepper>

Vertical

Account

Create your login

Profile

Tell us about yourself

Confirm

Review and finish

Step 2 of 3
vue-html
<SStepper :items="items" orientation="vertical" />

Color

One

Two

Three

Step 2 of 3

One

Two

Three

Step 2 of 3

One

Two

Three

Step 2 of 3

One

Two

Three

Step 2 of 3

One

Two

Three

Step 2 of 3

One

Two

Three

Step 2 of 3

One

Two

Three

Step 2 of 3
vue-html
<SStepper :items="items" color="success" />

Progress announcements

Stepper has one visually hidden, polite live region announcing the current step. Its English default is Step 1 of 2. Translate it globally through the message registry:

ts
export default defineAppConfig({
  selaras: {
    messages: {
      stepper: 'Progres',
      stepperProgress: (step, total) => `Langkah ${step} dari ${total}`,
    },
  },
})

stepper names the root group; an explicit aria-label on the component takes priority. stepperProgress(step, total) formats the live-region message. Changing selaras.locale formats locale-aware values but does not translate UI messages automatically. Update the message registry when switching languages; announcements react to those changes and to step or item-count changes.

For one instance, the progress slot replaces only the announcement text. Keep it concise; the component supplies the live-region semantics and hiding. An empty item list has no default announcement.

Atur kunci

Simpan kunci

Langkah 1 dari 2
vue-html
<SStepper v-model="step" :items="items" aria-label="Progres pembuatan kunci">
  <template #progress="{ step: currentStep, total }">
    Langkah {{ currentStep }} dari {{ total }}
  </template>
</SStepper>

In markdown

Every Selaras component is already globally registered under its S-prefixed name - a bare ::s-stepper{...} block works with no extra wiring (see Callout's own "In markdown" section for how the pattern works generally). items is an array prop, not a string - bind it with a leading : so @nuxtjs/mdc parses the attribute as an expression instead of literal text. Unlike Tabs or Accordion, each step's own text comes from its title/description fields directly, not a per-item slot - the block needs no body at all:

md
::s-stepper{:items='[{"title":"Cart"},{"title":"Shipping"},{"title":"Payment"}]'}
::

Custom :ui

Global and scoped recipe overrides use ui.stepper.

To see exactly what you'd be overriding - the current default classes for every slot and variant - here's Stepper's own theme file:

Theme source "stepper" not found

Props

PropTypeDefault
itemsStep[]-
modelValuenumber-
defaultValuenumber-
linearbooleantrue
orientation'horizontal' | 'vertical'horizontal
size'sm' | 'md' | 'lg'md
color'primary' | 'neutral' | 'secondary' | 'success' | 'danger' | 'info' | 'warning'primary
uiPartial<Record<StepperSlot, string | object>>-

Step is { title?: string, description?: string, icon?: string, disabled?: boolean }.

Events

EventPayloadDescription
update:modelValuenumberFires when a step's trigger is clicked

Slots

SlotPropsDescription
progress{ step, total }Replaces the screen-reader progress text; step may be undefined when no step is active
indicator{ item, index, state }Replaces the indicator's content (default: a checkmark once completed, else the item's icon, else the step number)
title{ item, index, state }Replaces the title text
description{ item, index, state }Replaces the description text

state is 'inactive' \| 'active' \| 'completed'.

Edit this page