{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "stepper",
  "title": "Stepper",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/stepper/Stepper.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, provide, toRef } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\nimport { STEPPER_CONTEXT, type StepperOrientation, type StepperSize, type StepperStatus } from './context'\nimport type { StepperStep } from './types'\n\ninterface Props {\n  steps?: StepperStep[]\n  modelValue?: number\n  orientation?: StepperOrientation\n  size?: StepperSize\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  steps: () => [],\n  modelValue: 1,\n  orientation: 'horizontal',\n  size: 'default',\n})\n\nconst emit = defineEmits<{\n  'update:modelValue': [value: number]\n}>()\n\nconst activeStep = computed(() => props.modelValue)\nconst stepsRef = computed(() => props.steps)\n\nfunction getStatus(index: number): StepperStatus {\n  const step = props.steps[index]\n  if (step?.error) return 'error'\n  if (index + 1 === activeStep.value) return 'active'\n  if (index + 1 < activeStep.value) return 'completed'\n  return 'pending'\n}\n\nfunction isClickable(index: number): boolean {\n  return index + 1 < activeStep.value\n}\n\nfunction goToStep(stepIndex: number) {\n  if (stepIndex < 1 || stepIndex > props.steps.length) return\n  const step = props.steps[stepIndex - 1]\n  if (step?.disabled) return\n  emit('update:modelValue', stepIndex)\n}\n\nprovide(STEPPER_CONTEXT, {\n  orientation: toRef(props, 'orientation'),\n  size: toRef(props, 'size'),\n  activeStep,\n  steps: stepsRef,\n  goToStep,\n  isClickable,\n  getStatus,\n})\n\ndefineExpose({ goToStep })\n</script>\n\n<template>\n  <div\n    :class=\"cn('w-full', props.class)\"\n    role=\"tablist\"\n    :aria-orientation=\"orientation\"\n    :data-orientation=\"orientation\"\n  >\n    <!-- Header strip with steps -->\n    <slot name=\"steps\">\n      <ol\n        v-if=\"steps.length > 0\"\n        :class=\"cn('flex', orientation === 'horizontal' ? 'flex-row items-start' : 'flex-col items-stretch')\"\n      >\n        <StepperItem v-for=\"(step, index) in steps\" :key=\"step.id\" :step=\"step\" :index=\"index\" />\n      </ol>\n    </slot>\n\n    <!-- Content area -->\n    <div v-if=\"$slots.default\" class=\"mt-6 flex-1\">\n      <slot :active-step=\"activeStep\" :steps=\"steps\" />\n    </div>\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/Stepper.vue"
    },
    {
      "path": "packages/registry-vue/components/stepper/StepperItem.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, inject } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\nimport { STEPPER_CONTEXT } from './context'\nimport type { StepperStep } from './types'\n\ninterface Props {\n  step: StepperStep\n  index: number\n  class?: HTMLAttributes['class']\n}\n\nconst props = defineProps<Props>()\n\nconst _maybeCtx = inject(STEPPER_CONTEXT)\nif (!_maybeCtx) throw new Error('StepperItem must be used inside <Stepper>')\nconst ctx: NonNullable<typeof _maybeCtx> = _maybeCtx\n\nconst status = computed(() => ctx.getStatus(props.index))\nconst orientation = computed(() => ctx.orientation.value)\nconst isFirst = computed(() => props.index === 0)\nconst isLast = computed(() => props.index === ctx.steps.value.length - 1)\nconst clickable = computed(() => ctx.isClickable(props.index) && !props.step.disabled)\n\n// A connector \"segment\" is the line drawn between this indicator and the\n// adjacent one. We split it into left/right halves so each item owns its\n// own piece — they butt up at item boundaries for pixel alignment.\nconst leftSegmentCompleted = computed(() => props.index < ctx.activeStep.value)\nconst rightSegmentCompleted = computed(() => props.index < ctx.activeStep.value - 1)\n\nfunction handleNavigate() {\n  if (clickable.value) ctx.goToStep(props.index + 1)\n}\n</script>\n\n<template>\n  <li\n    :class=\"\n      cn(\n        'group/stepper-item relative min-w-0',\n        orientation === 'horizontal'\n          ? 'flex flex-1 flex-col items-center gap-2'\n          : 'flex flex-row items-start gap-3 pb-6 last:pb-0',\n        step.disabled && 'opacity-50',\n        props.class,\n      )\n    \"\n    role=\"tab\"\n    :aria-selected=\"status === 'active'\"\n    :aria-disabled=\"step.disabled || undefined\"\n    :data-status=\"status\"\n  >\n    <!-- Indicator row: contains the indicator + connector segments -->\n    <div\n      :class=\"\n        cn(\n          'relative flex shrink-0',\n          orientation === 'horizontal'\n            ? 'h-9 w-full items-center justify-center'\n            : 'w-9 flex-col items-center justify-start self-stretch',\n        )\n      \"\n    >\n      <!-- Connector segments (absolute, butt up at item boundaries) -->\n      <span\n        v-if=\"!isFirst\"\n        aria-hidden=\"true\"\n        :class=\"\n          cn(\n            'pointer-events-none absolute transition-colors duration-200',\n            orientation === 'horizontal'\n              ? 'top-1/2 right-1/2 left-0 h-px -translate-y-1/2'\n              : 'top-0 bottom-1/2 left-1/2 w-px -translate-x-1/2',\n            leftSegmentCompleted ? 'bg-primary' : 'bg-border',\n          )\n        \"\n      />\n      <span\n        v-if=\"!isLast\"\n        aria-hidden=\"true\"\n        :class=\"\n          cn(\n            'pointer-events-none absolute transition-colors duration-200',\n            orientation === 'horizontal'\n              ? 'top-1/2 right-0 left-1/2 h-px -translate-y-1/2'\n              : 'top-1/2 bottom-0 left-1/2 w-px -translate-x-1/2',\n            rightSegmentCompleted ? 'bg-primary' : 'bg-border',\n          )\n        \"\n      />\n\n      <StepperIndicator\n        :status=\"status\"\n        :index=\"index + 1\"\n        :icon=\"step.icon\"\n        :clickable=\"clickable\"\n        class=\"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none\"\n        @click=\"handleNavigate\"\n      />\n    </div>\n\n    <!-- Title + description -->\n    <div :class=\"cn('min-w-0', orientation === 'horizontal' ? 'max-w-[12rem] text-center' : 'flex-1 pt-1.5')\">\n      <button\n        type=\"button\"\n        :class=\"\n          cn(\n            'text-foreground text-sm font-medium text-balance transition-colors outline-none',\n            clickable &&\n              'hover:text-primary focus-visible:text-primary focus-visible:ring-ring cursor-pointer focus-visible:ring-2 focus-visible:outline-none',\n            !clickable && 'cursor-default',\n            status === 'pending' && 'text-muted-foreground',\n            status === 'error' && 'text-destructive',\n          )\n        \"\n        :disabled=\"!clickable\"\n        @click=\"handleNavigate\"\n      >\n        {{ step.title }}\n      </button>\n      <p v-if=\"step.description\" class=\"text-muted-foreground mt-0.5 text-xs text-balance\">\n        {{ step.description }}\n      </p>\n    </div>\n  </li>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/StepperItem.vue"
    },
    {
      "path": "packages/registry-vue/components/stepper/StepperIndicator.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { Component, HTMLAttributes } from 'vue'\nimport { Check, X } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { stepperIndicatorVariants } from './stepper.variants'\nimport type { StepperSize, StepperStatus } from './context'\n\ninterface Props {\n  status?: StepperStatus\n  size?: StepperSize\n  index?: number\n  icon?: Component\n  clickable?: boolean\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  status: 'pending',\n  size: 'default',\n  clickable: false,\n})\n\nconst emit = defineEmits<{\n  click: []\n}>()\n\nconst fallbackIcon = computed(() => {\n  if (props.icon) return props.icon\n  if (props.status === 'completed') return Check\n  if (props.status === 'error') return X\n  return null\n})\n</script>\n\n<template>\n  <button\n    type=\"button\"\n    :class=\"\n      cn(\n        stepperIndicatorVariants({ status, size }),\n        'ring-background relative z-10 ring-4 transition-colors duration-200 outline-none',\n        clickable && 'focus-visible:ring-ring cursor-pointer focus-visible:ring-2 focus-visible:outline-none',\n        !clickable && 'cursor-default',\n        props.class,\n      )\n    \"\n    :disabled=\"!clickable\"\n    :aria-current=\"status === 'active' ? 'step' : undefined\"\n    @click=\"emit('click')\"\n  >\n    <slot>\n      <component :is=\"fallbackIcon\" v-if=\"fallbackIcon\" class=\"size-4\" aria-hidden=\"true\" />\n      <span v-else-if=\"index !== undefined\" class=\"font-medium\">{{ index }}</span>\n    </slot>\n  </button>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/StepperIndicator.vue"
    },
    {
      "path": "packages/registry-vue/components/stepper/StepperHeader.vue",
      "content": "<!--\n  StepperHeader Component\n  \n  Container for the stepper header row. Used to wrap StepperItems.\n  \n  @example\n  <StepperHeader>\n    <StepperItem>...</StepperItem>\n    <StepperSeparator />\n    <StepperItem>...</StepperItem>\n  </StepperHeader>\n-->\n<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\n\ninterface Props {\n  class?: HTMLAttributes['class']\n}\n\nconst props = defineProps<Props>()\n</script>\n\n<template>\n  <div :class=\"cn('stepper-header flex items-center gap-0', props.class)\">\n    <slot />\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/StepperHeader.vue"
    },
    {
      "path": "packages/registry-vue/components/stepper/StepperContent.vue",
      "content": "<!--\n  StepperContent Component\n  \n  Wrapper for step content with transition animations.\n  \n  @example\n  <StepperContent :step=\"currentStep\">\n    <div class=\"step-content\">\n      Step content here\n    </div>\n  </StepperContent>\n-->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\n\ninterface Props {\n  step?: number\n  activeStep?: number\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  step: 1,\n  activeStep: 1,\n})\n\nconst isActive = computed(() => props.step === props.activeStep)\n</script>\n\n<template>\n  <div\n    v-show=\"isActive\"\n    :class=\"cn('stepper-content motion-safe:animate-in motion-safe:fade-in-50 motion-safe:duration-200', props.class)\"\n    role=\"tabpanel\"\n    :aria-hidden=\"!isActive\"\n  >\n    <slot />\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/StepperContent.vue"
    },
    {
      "path": "packages/registry-vue/components/stepper/StepperTitle.vue",
      "content": "<!--\n  StepperTitle Component\n  \n  Title text for a stepper item.\n  \n  @example\n  <StepperTitle>Account Setup</StepperTitle>\n-->\n<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\n\ninterface Props {\n  class?: HTMLAttributes['class']\n}\n\nconst props = defineProps<Props>()\n</script>\n\n<template>\n  <span :class=\"cn('text-foreground text-sm font-medium', props.class)\">\n    <slot />\n  </span>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/StepperTitle.vue"
    },
    {
      "path": "packages/registry-vue/components/stepper/StepperDescription.vue",
      "content": "<!--\n  StepperDescription Component\n  \n  Description text for a stepper item.\n  \n  @example\n  <StepperDescription>Create your account to get started</StepperDescription>\n-->\n<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\n\ninterface Props {\n  class?: HTMLAttributes['class']\n}\n\nconst props = defineProps<Props>()\n</script>\n\n<template>\n  <span :class=\"cn('text-muted-foreground text-xs', props.class)\">\n    <slot />\n  </span>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/StepperDescription.vue"
    },
    {
      "path": "packages/registry-vue/components/stepper/StepperStep.vue",
      "content": "<!--\n  StepperStep Component\n  \n  A single step within a Stepper, used with step slots.\n  Provides title, description, completion state, and active state.\n  \n  @example\n  <Stepper v-model=\"currentStep\" orientation=\"vertical\">\n    <StepperStep \n      title=\"Account\" \n      description=\"Create your account\"\n      :completed=\"currentStep > 1\" \n      :active=\"currentStep === 1\"\n      :error=\"hasAccountError\"\n    >\n      <AccountForm @next=\"currentStep = 2\" />\n    </StepperStep>\n    <StepperStep \n      title=\"Profile\" \n      description=\"Set up your profile\"\n      :completed=\"currentStep > 2\" \n      :active=\"currentStep === 2\"\n    >\n      <ProfileForm @next=\"currentStep = 3\" />\n    </StepperStep>\n    <StepperStep \n      title=\"Confirm\" \n      description=\"Review and confirm\"\n      :completed=\"currentStep > 3\" \n      :active=\"currentStep === 3\"\n    >\n      <Confirmation @submit=\"handleSubmit\" />\n    </StepperStep>\n  </Stepper>\n-->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { Component, HTMLAttributes } from 'vue'\nimport { Check } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { stepperIndicatorVariants } from './stepper.variants'\n\ninterface Props {\n  title: string\n  description?: string\n  icon?: Component\n  completed?: boolean\n  active?: boolean\n  error?: boolean\n  disabled?: boolean\n  status?: 'active' | 'completed' | 'pending' | 'error'\n  index?: number\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  completed: false,\n  active: false,\n  error: false,\n  disabled: false,\n  index: undefined,\n})\n\nconst computedStatus = computed(() => {\n  if (props.status) return props.status\n  if (props.error) return 'error'\n  if (props.active) return 'active'\n  if (props.completed) return 'completed'\n  return 'pending'\n})\n</script>\n\n<template>\n  <div :class=\"cn('stepper-step flex gap-3', props.class)\" role=\"tab\" :aria-selected=\"active\" :aria-disabled=\"disabled\">\n    <!-- Indicator -->\n    <div\n      :class=\"\n        cn(\n          stepperIndicatorVariants({\n            status: computedStatus,\n            size: 'default',\n          }),\n        )\n      \"\n    >\n      <slot name=\"icon\">\n        <Check v-if=\"computedStatus === 'completed'\" class=\"size-4\" aria-hidden=\"true\" />\n        <span v-else-if=\"index\">{{ index }}</span>\n      </slot>\n    </div>\n\n    <!-- Content -->\n    <div class=\"flex flex-col gap-0.5 pt-1\">\n      <slot name=\"title\">\n        <span class=\"text-sm font-medium\">{{ title }}</span>\n      </slot>\n      <slot name=\"description\">\n        <span v-if=\"description\" class=\"text-muted-foreground text-xs\">\n          {{ description }}\n        </span>\n      </slot>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/StepperStep.vue"
    },
    {
      "path": "packages/registry-vue/components/stepper/context.ts",
      "content": "import type { InjectionKey, Ref } from 'vue'\nimport type { StepperStep } from './types'\n\nexport type StepperOrientation = 'horizontal' | 'vertical'\nexport type StepperStatus = 'active' | 'completed' | 'pending' | 'error'\nexport type StepperSize = 'sm' | 'default' | 'lg'\n\nexport interface StepperContext {\n  orientation: Ref<StepperOrientation>\n  size: Ref<StepperSize>\n  activeStep: Ref<number>\n  steps: Ref<StepperStep[]>\n  goToStep: (stepIndex: number) => void\n  isClickable: (index: number) => boolean\n  getStatus: (index: number) => StepperStatus\n}\n\nexport const STEPPER_CONTEXT: InjectionKey<StepperContext> = Symbol('StepperContext')\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/context.ts"
    },
    {
      "path": "packages/registry-vue/components/stepper/stepper.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\n/**\n * Variant definitions live in their own file (rather than the package\n * `index.ts`) so `StepperIndicator.vue` / `StepperStep.vue` can import\n * them without creating a circular dependency back through the index.\n * Sibling pattern to `card/card.variants.ts`.\n */\nexport const stepperIndicatorVariants = cva('flex items-center justify-center rounded-full font-semibold shrink-0', {\n  variants: {\n    status: {\n      pending: 'bg-muted text-muted-foreground',\n      active: 'bg-primary text-primary-foreground shadow-sm',\n      completed: 'bg-primary text-primary-foreground',\n      error: 'bg-destructive text-destructive-foreground',\n    },\n    size: {\n      sm: 'size-7 text-xs [&>svg]:size-3.5',\n      default: 'size-9 text-sm [&>svg]:size-4',\n      lg: 'size-11 text-base [&>svg]:size-5',\n    },\n  },\n  defaultVariants: {\n    status: 'pending',\n    size: 'default',\n  },\n})\n\nexport type StepperIndicatorVariants = VariantProps<typeof stepperIndicatorVariants>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/stepper.variants.ts"
    },
    {
      "path": "packages/registry-vue/components/stepper/types.ts",
      "content": "import type { Component } from 'vue'\n\nexport interface StepperStep {\n  id: string | number\n  title: string\n  description?: string\n  icon?: Component\n  disabled?: boolean\n  error?: boolean\n}\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/types.ts"
    },
    {
      "path": "packages/registry-vue/components/stepper/index.ts",
      "content": "export { default as Stepper } from './Stepper.vue'\nexport { default as StepperHeader } from './StepperHeader.vue'\nexport { default as StepperItem } from './StepperItem.vue'\nexport { default as StepperIndicator } from './StepperIndicator.vue'\nexport { default as StepperContent } from './StepperContent.vue'\nexport { default as StepperTitle } from './StepperTitle.vue'\nexport { default as StepperDescription } from './StepperDescription.vue'\nexport { default as StepperStep } from './StepperStep.vue'\nexport type { StepperStep as StepperStepConfig } from './types'\nexport type { StepperOrientation, StepperSize, StepperStatus } from './context'\n\n// Re-export variant API from the sibling file (kept separate to avoid the\n// StepperIndicator.vue <-> index.ts circular import that broke dev SSR).\nexport { stepperIndicatorVariants, type StepperIndicatorVariants } from './stepper.variants'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/stepper/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority",
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Multi-step indicator — horizontal or vertical, with completed / current / upcoming states and optional descriptions per step. Use for onboarding wizards and checkout flows.",
  "categories": [
    "navigation"
  ]
}