Stepper 步骤条

带有步骤状态与切换校验的步骤导航。

Step 2 of 0
第 2 步,共 3 步
<script setup lang="ts">
  import { Stepper } from '@hina-ui/vue'

  const items = [
    { title: '步骤 A', description: '第一项的说明' },
    { title: '步骤 B', description: '第二项包含更长的说明文字,支持自然换行' },
    { title: '步骤 C', description: '最后一项的说明' },
  ]
</script>

<template>
  <Stepper :items="items" :default-value="2" />
</template>
vue

用法

import { Stepper, type StepperItem } from '@hina-ui/vue'
ts

items 定义步骤,v-model 是从 1 开始的当前步骤编号。默认从第一步开始;不绑定 v-model 时由组件管理当前步骤,可用 defaultValue 设置初始值。

Step 1 of 0
第 1 步,共 3 步

当前步骤:1

<script setup lang="ts">
  import { ref } from 'vue'
  import { Stepper, Text, Stack } from '@hina-ui/vue'

  const step = ref(1)
  const items = [
    { title: '步骤 A', description: '第一项的说明' },
    { title: '步骤 B', description: '第二项包含更长的说明文字,支持自然换行' },
    { title: '步骤 C', description: '最后一项的说明' },
  ]
</script>

<template>
  <Stack class="w-full" gap="lg">
    <Stepper v-model="step" :items="items" />
    <Text size="sm" tone="muted">当前步骤:{{ step }}</Text>
  </Stack>
</template>
vue

示例

线性与自由切换

linear 默认为 true:可以回到之前的步骤,也可以进入紧邻的下一步,但不能直接跳过中间步骤。设为 false 后,可进入任意未禁用步骤。

线性限制只决定可切换的范围,不代表数据已经通过校验。需要校验时使用 beforeChange。直接从外部设置 v-model 是调用方主动更新状态,不经过切换限制和校验。

Step 1 of 0
第 1 步,共 3 步
<script setup lang="ts">
  import { ref } from 'vue'
  import { Stepper, Stack, Switch } from '@hina-ui/vue'

  const linear = ref(true)
  const items = [
    { title: '步骤 A', description: '第一项的说明' },
    { title: '步骤 B', description: '第二项包含更长的说明文字,支持自然换行' },
    { title: '步骤 C', description: '最后一项的说明' },
  ]
</script>

<template>
  <Stack class="w-full" gap="lg">
    <Switch v-model="linear">线性切换</Switch>
    <Stepper :items="items" :linear="linear" />
  </Stack>
</template>
vue

纵向

orientation="vertical" 将文字放在节点旁,连线随说明长度延伸。横向布局将文字放在节点下方,各步骤等宽;方向变化同时影响方向键导航。

Step 2 of 0
第 2 步,共 3 步
<script setup lang="ts">
  import { Stepper } from '@hina-ui/vue'

  const items = [
    { title: '步骤 A', description: '第一项的说明' },
    { title: '步骤 B', description: '第二项包含更长的说明文字,支持自然换行' },
    { title: '步骤 C', description: '最后一项的说明' },
  ]
</script>

<template>
  <Stepper :items="items" :default-value="2" orientation="vertical" class="max-w-md" />
</template>
vue

状态

当前步骤之前的步骤默认显示完成标记;completed 可显式标记完成,包括最后一步。error 优先显示错误标记,当前步骤仍保留其当前位置语义。

单项 disabled 禁止进入该步骤,根 disabled 禁止所有用户切换。next()、prev() 不会跳过禁用项;相邻项被禁用时,对应方法返回 false。

Step 2 of 0
第 2 步,共 3 步
<script setup lang="ts">
  import { Stepper, Stack, Switch } from '@hina-ui/vue'
  import { ref } from 'vue'

  const disabled = ref(false)
  const items = [
    { title: '已完成', completed: true },
    { title: '需要检查', description: '此步骤存在错误', error: true },
    { title: '已禁用', disabled: true },
  ]
</script>

<template>
  <Stack class="w-full" gap="lg">
    <Switch v-model="disabled">禁用整个步骤条</Switch>
    <Stepper :items="items" :default-value="2" :linear="false" :disabled="disabled" />
  </Stack>
</template>
vue

内容与导航

默认插槽接收当前步骤与导航方法。可组合 Card、Button 等组件;只有使用插槽时才渲染内容区。

内容插槽始终挂载,如何切换、保留或重置内部内容由调用方决定。组件实例也暴露 next()、prev()、goTo(step),与插槽内的方法行为一致。

Step 1 of 0

步骤 A

这里是第 1 步的内容。

第 1 步,共 3 步
<script setup lang="ts">
  import { Stepper, Button, Card, Inline, Stack, Text } from '@hina-ui/vue'

  const items = [
    { title: '步骤 A', description: '第一项的说明' },
    { title: '步骤 B', description: '第二项包含更长的说明文字,支持自然换行' },
    { title: '步骤 C', description: '最后一项的说明' },
  ]
</script>

<template>
  <Stepper :items="items">
    <template #default="{ item, step, next, prev, canNext, canPrev }">
      <Card>
        <Stack gap="lg">
          <Text weight="medium">{{ item?.title }}</Text>
          <Text tone="muted">这里是第 {{ step }} 步的内容。</Text>
          <Inline justify="between">
            <Button variant="outline" tone="neutral" :disabled="!canPrev" @click="prev">
              上一步
            </Button>
            <Button :disabled="!canNext" @click="next">下一步</Button>
          </Inline>
        </Stack>
      </Card>
    </template>
  </Stepper>
</template>
vue

切换前校验

beforeChange(nextStep, previousStep) 在点击步骤、键盘激活或调用导航方法时执行。返回 false 阻止切换;返回 true 或不返回值允许切换,也可返回 Promise。

等待期间保持当前步骤并阻止重复切换。抛错或 Promise 拒绝时保留当前步骤,通过 error 事件交给调用方处理。外部修改当前步骤、步骤结构或禁用状态,以及组件卸载后,旧校验结果不会再推进步骤。

示例使用 FormField 和 Input。点击第二步的标题与点击“下一步”会经过同一个校验函数。

Step 1 of 0
第 1 步,共 3 步
<script setup lang="ts">
  import { computed, ref } from 'vue'
  import { Stepper, Button, Card, FormField, Input, Inline, Stack, Text } from '@hina-ui/vue'

  const name = ref('')
  const error = ref('')
  const items = computed(() => [
    { title: '步骤 A', error: !!error.value },
    { title: '步骤 B' },
    { title: '步骤 C' },
  ])

  async function beforeChange(step: number, previousStep: number) {
    if (step <= previousStep || previousStep !== 1) return true
    await new Promise(resolve => setTimeout(resolve, 700))
    error.value = name.value.trim() ? '' : '请填写名称'
    return !error.value
  }
</script>

<template>
  <Stepper :items="items" :before-change="beforeChange">
    <template #default="{ step, next, prev, pending, canNext, canPrev }">
      <Card>
        <Stack gap="lg">
          <FormField v-if="step === 1" label="名称" :error="error">
            <Input v-model="name" :disabled="pending" />
          </FormField>
          <Text v-else>第 {{ step }} 步</Text>
          <Inline justify="between">
            <Button variant="outline" tone="neutral" :disabled="!canPrev" @click="prev">
              上一步
            </Button>
            <Button :loading="pending" :disabled="!canNext" @click="next">下一步</Button>
          </Inline>
        </Stack>
      </Card>
    </template>
  </Stepper>
</template>
vue

自定义指示器与文字

#indicator、#title、#description 接收原始 item、从 0 开始的 index、从 1 开始的 step、state、active、pending 与 disabled。自定义字段会保留类型推导。

示例用图标替换默认编号,并在标题中加入 Tag。这些插槽位于步骤按钮内部,应只放非交互内容;输入框、链接和其他按钮放在默认内容插槽中。

Step 1 of 0
第 1 步,共 3 步
<script setup lang="ts">
  import { FileText, ListChecks, Send } from '@lucide/vue'
  import { Stepper, Tag, Inline, Text, type StepperItem } from '@hina-ui/vue'

  interface Item extends StepperItem {
    note: string
  }
  const icons = [FileText, ListChecks, Send]
  const items: Item[] = [
    { title: '步骤 A', note: '必填' },
    { title: '步骤 B', note: '可选' },
    { title: '步骤 C', note: '确认' },
  ]
</script>

<template>
  <Stepper :items="items" :linear="false">
    <template #indicator="{ index }"><component :is="icons[index]" /></template>
    <template #title="{ item, active }">
      <Inline as="span" justify="center" gap="sm">
        <Text as="span" weight="medium">{{ item.title }}</Text>
        <Tag :tone="active ? 'accent' : 'neutral'">{{ item.note }}</Tag>
      </Inline>
    </template>
  </Stepper>
</template>
vue

尺寸

size 调整指示器与文字大小。节点尺寸和间距使用 Hina 变量,随密度设置变化。

sm

Step 2 of 0
第 2 步,共 3 步

md

Step 2 of 0
第 2 步,共 3 步

lg

Step 2 of 0
第 2 步,共 3 步
<script setup lang="ts">
  import { Stepper, Stack, Text, type StepperSize } from '@hina-ui/vue'

  const sizes: StepperSize[] = ['sm', 'md', 'lg']
  const items = [
    { title: '步骤 A', description: '第一项的说明' },
    { title: '步骤 B', description: '第二项包含更长的说明文字,支持自然换行' },
    { title: '步骤 C', description: '最后一项的说明' },
  ]
</script>

<template>
  <Stack class="w-full" gap="lg">
    <Stack v-for="size in sizes" :key="size">
      <Text size="sm" tone="muted">{{ size }}</Text>
      <Stepper :items="items" :size="size" :default-value="2" />
    </Stack>
  </Stack>
</template>
vue

RTL

方向继承外层 dir 或配置提供器,也可用 dir="rtl" 指定。横向排列、连线和方向键导航共同反转。

Step 1 of 0
第 1 步,共 3 步
<script setup lang="ts">
  import { Stepper } from '@hina-ui/vue'

  const items = [
    { title: 'الخطوة الأولى', description: 'وصف الخطوة الأولى' },
    { title: 'الخطوة الثانية', description: 'وصف الخطوة الثانية' },
    { title: 'الخطوة الثالثة' },
  ]
</script>

<template>
  <Stepper :items="items" :linear="false" dir="rtl" />
</template>
vue

行为与无障碍

  • 步骤组成有名称的列表组,label 可替换默认无障碍名称。
  • 每一步是真实按钮,标题和可选描述参与命名;当前按钮带 aria-current="step"。错误和完成状态同时提供文字,不仅依靠颜色与图标。
  • Tab 在可操作步骤和内容之间移动;横向用左右方向键,纵向用上下方向键移动焦点,Enter 或空格激活。移动焦点不会自动切换步骤。
  • 导航不会主动抢走内容区焦点,也不会自动提交表单。
  • 进度播报随界面语言变化。减弱动态效果设置由 Hina 动效变量统一处理。

API

Props

属性
类型
默认值
说明
items
T[]
必填
步骤列表,T extends StepperItem
v-model
number
—
当前步骤,编号从 1 开始
defaultValue
number
1
非受控初始步骤
orientation
'horizontal' | 'vertical'
'horizontal'
排布与键盘导航方向
size
'sm' | 'md' | 'lg'
'md'
尺寸
linear
boolean
true
限制顺序切换
disabled
boolean
false
禁止所有用户切换
beforeChange
StepperBeforeChange
—
切换前校验
label
string
取自界面语言
步骤组的无障碍名
dir
'ltr' | 'rtl'
继承
阅读方向
class
string
—
根元素的类

空列表的显示步骤为 0;越界或无效的当前值仅在呈现时收敛到有效范围,不自动写回模型。defaultValue 只用于初始化。

StepperItem

字段
类型
说明
title
string
必填,步骤标题
description
string
可选说明
disabled
boolean
禁止进入该步骤
completed
boolean
显式显示完成;设为 false 不覆盖之前步骤的自动完成态
error
boolean
错误状态,优先于完成态

步骤编号和身份由数组位置决定;index 从 0 开始,step 从 1 开始。

Slots

插槽
参数
说明
default
StepperNavigation<T>
当前内容与导航操作
indicator
StepperSlotProps<T>
节点,默认编号、勾、错误图标或等待指示
title
StepperSlotProps<T>
标题
description
StepperSlotProps<T>
说明

StepperSlotProps<T> 为 { item, index, step, state, active, pending, disabled }。state 是 'inactive' | 'active' | 'completed' | 'error';pending 表示该项正在等待切换校验,disabled 表示该项被显式禁用或被线性范围限制。

StepperNavigation<T> 为 { step, item, total, pending, canNext, canPrev, next, prev, goTo }。空列表时 item 为 undefined。导航方法返回 Promise<boolean>,实际发起模型更新时为 true,被阻止、无变化或校验过期时为 false。

Events

事件
参数
说明
update:modelValue
step: number
请求更新当前步骤
error
error: unknown
切换校验抛出的异常

Expose

step、pending、canNext、canPrev 和 next()、prev()、goTo(step) 可通过组件引用访问。beforeChange 为 (nextStep: number, previousStep: number) => boolean | void | Promise<boolean | void>。