Timeline 时间线

以相连的节点按顺序呈现事件。

  1. 条目 A
    第一项的说明文字。
  2. 条目 B
    这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
  3. 条目 C
    最后一项不继续绘制连线。
<script setup lang="ts">
  import { Timeline } from '@hina-ui/vue'

  const items = [
    {
      id: 'a',
      title: '条目 A',
      description: '第一项的说明文字。',
      time: '09:00',
      dateTime: '2026-09-16T09:00:00+08:00',
    },
    {
      id: 'b',
      title: '条目 B',
      description:
        '这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。',
      time: '09:20',
      dateTime: '2026-09-16T09:20:00+08:00',
    },
    {
      id: 'c',
      title: '条目 C',
      description: '最后一项不继续绘制连线。',
      time: '09:40',
      dateTime: '2026-09-16T09:40:00+08:00',
    },
  ]
</script>

<template>
  <Timeline :items="items" class="max-w-lg" />
</template>
vue

用法

import { Timeline, type TimelineItem } from '@hina-ui/vue'
ts

items 定义节点顺序,每项可包含 title、description 和 time。连线随内容高度延伸,最后一项没有尾线。条目会更新或重排时,提供稳定的 id。

  1. 条目 A
    第一项的说明文字。
  2. 条目 B
    这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
  3. 条目 C
    最后一项不继续绘制连线。
<script setup lang="ts">
  import { Timeline } from '@hina-ui/vue'

  const items = [
    {
      id: 'a',
      title: '条目 A',
      description: '第一项的说明文字。',
      time: '09:00',
      dateTime: '2026-09-16T09:00:00+08:00',
    },
    {
      id: 'b',
      title: '条目 B',
      description:
        '这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。',
      time: '09:20',
      dateTime: '2026-09-16T09:20:00+08:00',
    },
    {
      id: 'c',
      title: '条目 C',
      description: '最后一项不继续绘制连线。',
      time: '09:40',
      dateTime: '2026-09-16T09:40:00+08:00',
    },
  ]
</script>

<template>
  <Timeline :items="items" />
</template>
vue

示例

节点颜色

tone 设置默认节点颜色,单项的 tone 可覆盖它。颜色不表示隐含的完成或选中状态,状态信息需要同时用文字表达。

  1. accent
  2. neutral
  3. success
  4. warning
  5. danger
  6. info
<script setup lang="ts">
  import { Timeline, type TimelineTone } from '@hina-ui/vue'

  const tones: TimelineTone[] = ['accent', 'neutral', 'success', 'warning', 'danger', 'info']
  const items = tones.map(tone => ({ id: tone, title: tone, tone }))
</script>

<template>
  <Timeline :items="items" />
</template>
vue

对齐方式

align="start" 将节点轴放在起始侧,内容排在轴后;end 将轴放在结束侧,内容排在轴前;alternate 将轴居中,内容交替排在两侧。纵向交替排布时,两侧等宽。

  1. 条目 A
    第一项的说明文字。
  2. 条目 B
    这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
  3. 条目 C
    最后一项不继续绘制连线。
<script setup lang="ts">
  import { ref } from 'vue'
  import { Timeline, Select, Stack, type TimelineAlign } from '@hina-ui/vue'

  const align = ref<TimelineAlign>('alternate')
  const options = [
    { value: 'start', label: 'start' },
    { value: 'end', label: 'end' },
    { value: 'alternate', label: 'alternate' },
  ]
  const items = [
    {
      id: 'a',
      title: '条目 A',
      description: '第一项的说明文字。',
      time: '09:00',
      dateTime: '2026-09-16T09:00:00+08:00',
    },
    {
      id: 'b',
      title: '条目 B',
      description:
        '这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。',
      time: '09:20',
      dateTime: '2026-09-16T09:20:00+08:00',
    },
    {
      id: 'c',
      title: '条目 C',
      description: '最后一项不继续绘制连线。',
      time: '09:40',
      dateTime: '2026-09-16T09:40:00+08:00',
    },
  ]
</script>

<template>
  <Stack class="w-full" gap="lg">
    <Select v-model="align" :options="options" aria-label="对齐方式" class="w-40" />
    <Timeline :items="items" :align="align" />
  </Stack>
</template>
vue

对侧时间

time-position="opposite" 将时间移到轴的另一侧,纵向两侧等宽。#opposite 可替换对侧区域;设置该插槽会预留对侧空间。

time 是直接显示的文字,dateTime 为其补充机器可读的值。提供 dateTime 时默认时间渲染为 time 元素;需要本地化格式时,可通过 #time 放入 Time。

  1. 条目 A
    第一项的说明文字。
  2. 条目 B
    这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
  3. 条目 C
    最后一项不继续绘制连线。
<script setup lang="ts">
  import { Timeline } from '@hina-ui/vue'

  const items = [
    {
      id: 'a',
      title: '条目 A',
      description: '第一项的说明文字。',
      time: '09:00',
      dateTime: '2026-09-16T09:00:00+08:00',
    },
    {
      id: 'b',
      title: '条目 B',
      description:
        '这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。',
      time: '09:20',
      dateTime: '2026-09-16T09:20:00+08:00',
    },
    {
      id: 'c',
      title: '条目 C',
      description: '最后一项不继续绘制连线。',
      time: '09:40',
      dateTime: '2026-09-16T09:40:00+08:00',
    },
  ]
</script>

<template>
  <Timeline :items="items" time-position="opposite" />
</template>
vue

横向

orientation="horizontal" 将节点水平排列。start 的正文位于轴下,end 位于轴上,alternate 上下交替。不同长度的内容仍共用同一条水平轴。

横向条目等分可用宽度;条目较多时,可以设置最小宽度并放入 ScrollArea。组件不会自动改变方向或裁切内容。

  1. 条目 A
    第一项的说明文字。
  2. 条目 B
    这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
  3. 条目 C
    最后一项不继续绘制连线。
<script setup lang="ts">
  import { ref } from 'vue'
  import { Timeline, Select, Stack, ScrollArea, type TimelineAlign } from '@hina-ui/vue'

  const align = ref<TimelineAlign>('start')
  const options = [
    { value: 'start', label: 'start' },
    { value: 'end', label: 'end' },
    { value: 'alternate', label: 'alternate' },
  ]
  const items = [
    {
      id: 'a',
      title: '条目 A',
      description: '第一项的说明文字。',
      time: '09:00',
      dateTime: '2026-09-16T09:00:00+08:00',
    },
    {
      id: 'b',
      title: '条目 B',
      description:
        '这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。',
      time: '09:20',
      dateTime: '2026-09-16T09:20:00+08:00',
    },
    {
      id: 'c',
      title: '条目 C',
      description: '最后一项不继续绘制连线。',
      time: '09:40',
      dateTime: '2026-09-16T09:40:00+08:00',
    },
  ]
</script>

<template>
  <Stack class="w-full" gap="lg">
    <Select v-model="align" :options="options" aria-label="对齐方式" class="w-40" />
    <ScrollArea direction="horizontal" :shadow="false" class="w-full">
      <Timeline :items="items" orientation="horizontal" :align="align" class="min-w-lg" />
    </ScrollArea>
  </Stack>
</template>
vue

尺寸

size 调整文字与节点间距。间距跟随密度变量,默认是 md。

sm

  1. 条目 A
    第一项的说明文字。
  2. 条目 B
    这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
  3. 条目 C
    最后一项不继续绘制连线。

md

  1. 条目 A
    第一项的说明文字。
  2. 条目 B
    这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
  3. 条目 C
    最后一项不继续绘制连线。

lg

  1. 条目 A
    第一项的说明文字。
  2. 条目 B
    这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
  3. 条目 C
    最后一项不继续绘制连线。
<script setup lang="ts">
  import { SimpleGrid, Stack, Text, Timeline, type TimelineSize } from '@hina-ui/vue'

  const sizes: TimelineSize[] = ['sm', 'md', 'lg']
  const items = [
    {
      id: 'a',
      title: '条目 A',
      description: '第一项的说明文字。',
      time: '09:00',
      dateTime: '2026-09-16T09:00:00+08:00',
    },
    {
      id: 'b',
      title: '条目 B',
      description:
        '这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。',
      time: '09:20',
      dateTime: '2026-09-16T09:20:00+08:00',
    },
    {
      id: 'c',
      title: '条目 C',
      description: '最后一项不继续绘制连线。',
      time: '09:40',
      dateTime: '2026-09-16T09:40:00+08:00',
    },
  ]
</script>

<template>
  <SimpleGrid min="12rem" class="w-full" gap="lg">
    <Stack v-for="size in sizes" :key="size">
      <Text size="sm" tone="muted">{{ size }}</Text>
      <Timeline :items="items" :size="size" />
    </Stack>
  </SimpleGrid>
</template>
vue

自定义节点与内容

#marker 替换节点,可组合 Avatar、Spinner 或图标。#content 替换整个正文区域,包括默认时间、标题和描述;需要只改某一部分时,用 #time、#title 或 #description。

所有插槽接收 { item, index },item 保留自定义字段的类型,index 是当前显示顺序中的位置。示例使用 Card 和 Tag 组合正文,并在等待中的条目上显示加载指示。

  1. 已完成

    完成

    内容区域可组合其他组件,节点可以换成头像。

    Hina

  2. 已补充

    完成

    通过插槽读取条目上的自定义字段。

    Hina

  3. 进行中

    进行中

    等待中的节点可以使用加载指示。

    Hina

<script setup lang="ts">
  import { Check } from '@lucide/vue'
  import {
    Avatar,
    Card,
    Inline,
    Spinner,
    Stack,
    Tag,
    Text,
    Time,
    Timeline,
    type TimelineItem,
  } from '@hina-ui/vue'

  interface Item extends TimelineItem {
    author: string
    pending: boolean
  }

  const items: Item[] = [
    {
      id: 'a',
      title: '已完成',
      description: '内容区域可组合其他组件,节点可以换成头像。',
      time: '09:00',
      dateTime: '2026-09-16T09:00:00+08:00',
      author: 'Hina',
      pending: false,
      tone: 'success',
    },
    {
      id: 'b',
      title: '已补充',
      description: '通过插槽读取条目上的自定义字段。',
      time: '09:20',
      dateTime: '2026-09-16T09:20:00+08:00',
      author: 'Hina',
      pending: false,
      tone: 'success',
    },
    {
      id: 'c',
      title: '进行中',
      description: '等待中的节点可以使用加载指示。',
      time: '09:40',
      dateTime: '2026-09-16T09:40:00+08:00',
      author: 'Hina',
      pending: true,
      tone: 'accent',
    },
  ]
</script>

<template>
  <Timeline :items="items" class="max-w-xl">
    <template #marker="{ item, index }">
      <Avatar v-if="index === 0" :name="item.author" size="sm" />
      <Spinner v-else-if="item.pending" size="sm" />
      <Check v-else />
    </template>
    <template #content="{ item }">
      <Card>
        <Stack gap="sm">
          <Inline justify="between">
            <Text weight="medium">{{ item.title }}</Text>
            <Tag :tone="item.pending ? 'accent' : 'success'">
              {{ item.pending ? '进行中' : '完成' }}
            </Tag>
          </Inline>
          <Text size="sm" tone="muted">{{ item.description }}</Text>
          <Inline gap="sm">
            <Text size="xs" tone="muted">{{ item.author }}</Text>
            <Time :value="item.dateTime" format="time" class="text-muted text-xs" />
          </Inline>
        </Stack>
      </Card>
    </template>
  </Timeline>
</template>
vue

反向顺序

reverse 反转实际渲染顺序,不修改传入的数组。连线终点、插槽索引和交替位置都按显示顺序计算。

  1. 条目 A
    第一项的说明文字。
  2. 条目 B
    这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
  3. 条目 C
    最后一项不继续绘制连线。
<script setup lang="ts">
  import { ref } from 'vue'
  import { Stack, Switch, Timeline } from '@hina-ui/vue'

  const reverse = ref(false)
  const items = [
    {
      id: 'a',
      title: '条目 A',
      description: '第一项的说明文字。',
      time: '09:00',
      dateTime: '2026-09-16T09:00:00+08:00',
    },
    {
      id: 'b',
      title: '条目 B',
      description:
        '这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。',
      time: '09:20',
      dateTime: '2026-09-16T09:20:00+08:00',
    },
    {
      id: 'c',
      title: '条目 C',
      description: '最后一项不继续绘制连线。',
      time: '09:40',
      dateTime: '2026-09-16T09:40:00+08:00',
    },
  ]
</script>

<template>
  <Stack class="w-full" gap="lg">
    <Switch v-model="reverse">反向排列</Switch>
    <Timeline :items="items" :reverse="reverse" />
  </Stack>
</template>
vue

RTL

方向继承外层的 dir,也可直接传入 dir="rtl"。纵向起始侧与结束侧、横向节点顺序都会跟随方向。

  1. 09:00
    البند الأول
    وصف موجز للبند الأول.
  2. 09:20
    البند الثاني
    وصف البند الثاني.
  3. 09:40
    البند الثالث
<script setup lang="ts">
  import { Timeline } from '@hina-ui/vue'

  const items = [
    { id: 'a', title: 'البند الأول', description: 'وصف موجز للبند الأول.', time: '09:00' },
    { id: 'b', title: 'البند الثاني', description: 'وصف البند الثاني.', time: '09:20' },
    { id: 'c', title: 'البند الثالث', time: '09:40' },
  ]
</script>

<template>
  <Timeline :items="items" dir="rtl" />
</template>
vue

无障碍

  • 根元素是有序列表,条目按 DOM 顺序阅读;可通过 aria-label 命名。
  • 节点和连线是装饰,对辅助技术隐藏。节点插槽应只放非交互内容;链接、按钮放在正文或对侧插槽中,保持正常键盘操作。
  • 不增加焦点停靠点,也不自动赋予选中、完成或当前步骤语义。
  • 默认不包含动画;自定义加载指示遵循其自身的减弱动态效果设置。

API

Props

属性
类型
默认值
说明
items
T[]
必填
按顺序排列的条目,T extends TimelineItem
orientation
'vertical' | 'horizontal'
'vertical'
排布方向
align
'start' | 'end' | 'alternate'
'start'
节点轴与内容的相对位置
size
'sm' | 'md' | 'lg'
'md'
文字与节点间距
tone
TimelineTone
'accent'
默认节点颜色
timePosition
'content' | 'opposite'
'content'
默认时间的位置
reverse
boolean
false
反转显示顺序
class
string
—
根元素的类

原生属性(如 dir、aria-label、style)透传到根元素。

TimelineItem

字段
类型
说明
id
string | number
稳定的条目标识;省略时使用显示索引
title
string
标题
description
string
描述
time
string
直接显示的时间文字
dateTime
string
time 元素的 datetime 属性,不参与格式化或排序
tone
TimelineTone
覆盖该项的节点颜色

TimelineTone:'accent' | 'neutral' | 'success' | 'warning' | 'danger' | 'info'。所有条目字段均可选,插槽可以完全接管呈现。

Slots

插槽
参数
说明
marker
{ item: T, index: number }
节点装饰,默认空心圆点
content
{ item: T, index: number }
整个正文区域
title
{ item: T, index: number }
标题
description
{ item: T, index: number }
描述
time
{ item: T, index: number }
时间,位置由 timePosition 决定
opposite
{ item: T, index: number }
对侧区域,优先于对侧时间

同时导出 TimelineItem、TimelineSlotProps<T>、TimelineTone、TimelineSize、TimelineOrientation 和 TimelineAlign 类型。