Skip to main content
A vertical navigation component that shows progress through a multi-step process. Each step displays an icon and label, with visual states for completed, current, and pending steps.

Component Location

services/frontend/src/components/ui/ds-stepper/
- DsStepper.vue    # Main stepper component
- index.ts         # Exports

Props

PropTypeDefaultDescription
stepsWizardStep[]requiredArray of step objects
interactivebooleanfalseAllow clicking completed steps to navigate back

WizardStep Interface

interface WizardStep {
  id: string
  label: string
  status: 'completed' | 'current' | 'pending'
}

Events

EventPayloadDescription
stepClick[step: WizardStep, index: number]Emitted when a completed step is clicked (requires interactive prop)

Usage

Import the component and type from the ds-stepper index:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { DsStepper, type WizardStep } from '@/components/ui/ds-stepper'

const currentStep = ref(0)

const steps = [
  { key: 'repository', label: 'Repository' },
  { key: 'config', label: 'Configuration' },
  { key: 'review', label: 'Review' }
]

const wizardSteps = computed((): WizardStep[] => {
  return steps.map((step, index) => ({
    id: step.key,
    label: step.label,
    status: index < currentStep.value
      ? 'completed'
      : index === currentStep.value
        ? 'current'
        : 'pending'
  }))
})

function handleStepClick(step: WizardStep, index: number) {
  if (step.status === 'completed') {
    currentStep.value = index
  }
}
</script>

<template>
  <div class="flex gap-8">
    <DsStepper
      :steps="wizardSteps"
      interactive
      @step-click="handleStepClick"
    />

    <div class="flex-1">
      <!-- Step content -->
    </div>
  </div>
</template>

Visual States

Each step renders differently based on its status:
StatusIconIcon ColorLabel Color
completedCircleChecktext-green-600text-muted-foreground
currentCircleDashedtext-primarytext-foreground
pendingCircleDashedtext-neutral-400text-neutral-400

Interactive Mode

When interactive is true, completed steps become clickable:
  • Hover state: bg-neutral-100
  • Cursor changes to pointer
  • Clicking emits stepClick event
Only completed steps respond to clicks. Current and pending steps remain non-interactive regardless of the interactive prop.
<DsStepper
  :steps="wizardSteps"
  interactive
  @step-click="goToStep"
/>

Layout

The stepper is designed to sit in a flex container alongside step content:
<template>
  <div class="flex gap-8">
    <!-- Stepper on left (fixed width) -->
    <DsStepper :steps="steps" />

    <!-- Content on right (flexible width) -->
    <div class="flex-1">
      <DsCard v-if="currentStep === 0" title="Step 1">
        <!-- Step 1 content -->
      </DsCard>
    </div>
  </div>
</template>
The stepper has a fixed width of w-56 (224px) and uses shrink-0 to prevent compression.

Styling

The component uses shadcn-vue design tokens:
  • Container: w-56 shrink-0
  • List spacing: space-y-1
  • Item padding: px-3 py-2
  • Icon size: h-5 w-5
  • Label: text-sm font-medium