- 条目 A第一项的说明文字。
- 条目 B这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
- 条目 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>
用法
import { Timeline, type TimelineItem } from '@hina-ui/vue'
items 定义节点顺序,每项可包含 title、description 和 time。连线随内容高度延伸,最后一项没有尾线。条目会更新或重排时,提供稳定的 id。
- 条目 A第一项的说明文字。
- 条目 B这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
- 条目 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>
示例
节点颜色
tone 设置默认节点颜色,单项的 tone 可覆盖它。颜色不表示隐含的完成或选中状态,状态信息需要同时用文字表达。
- accent
- neutral
- success
- warning
- danger
- 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>
对齐方式
align="start" 将节点轴放在起始侧,内容排在轴后;end 将轴放在结束侧,内容排在轴前;alternate 将轴居中,内容交替排在两侧。纵向交替排布时,两侧等宽。
- 条目 A第一项的说明文字。
- 条目 B这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
- 条目 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>
对侧时间
time-position="opposite" 将时间移到轴的另一侧,纵向两侧等宽。#opposite 可替换对侧区域;设置该插槽会预留对侧空间。
time 是直接显示的文字,dateTime 为其补充机器可读的值。提供 dateTime 时默认时间渲染为 time 元素;需要本地化格式时,可通过 #time 放入 Time。
- 条目 A第一项的说明文字。
- 条目 B这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
- 条目 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>
横向
orientation="horizontal" 将节点水平排列。start 的正文位于轴下,end 位于轴上,alternate 上下交替。不同长度的内容仍共用同一条水平轴。
横向条目等分可用宽度;条目较多时,可以设置最小宽度并放入 ScrollArea。组件不会自动改变方向或裁切内容。
- 条目 A第一项的说明文字。
- 条目 B这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
- 条目 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>
尺寸
size 调整文字与节点间距。间距跟随密度变量,默认是 md。
sm
- 条目 A第一项的说明文字。
- 条目 B这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
- 条目 C最后一项不继续绘制连线。
md
- 条目 A第一项的说明文字。
- 条目 B这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
- 条目 C最后一项不继续绘制连线。
lg
- 条目 A第一项的说明文字。
- 条目 B这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
- 条目 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>
自定义节点与内容
#marker 替换节点,可组合 Avatar、Spinner 或图标。#content 替换整个正文区域,包括默认时间、标题和描述;需要只改某一部分时,用 #time、#title 或 #description。
所有插槽接收 { item, index },item 保留自定义字段的类型,index 是当前显示顺序中的位置。示例使用 Card 和 Tag 组合正文,并在等待中的条目上显示加载指示。
已完成
完成内容区域可组合其他组件,节点可以换成头像。
Hina
已补充
完成通过插槽读取条目上的自定义字段。
Hina
进行中
进行中等待中的节点可以使用加载指示。
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>
反向顺序
reverse 反转实际渲染顺序,不修改传入的数组。连线终点、插槽索引和交替位置都按显示顺序计算。
- 条目 A第一项的说明文字。
- 条目 B这一项包含更长的说明文字,换行后节点之间的连线仍然连续,后面的内容随之向下排列。
- 条目 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>
RTL
方向继承外层的 dir,也可直接传入 dir="rtl"。纵向起始侧与结束侧、横向节点顺序都会跟随方向。
- 09:00البند الأولوصف موجز للبند الأول.
- 09:20البند الثانيوصف البند الثاني.
- 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>
无障碍
- 根元素是有序列表,条目按 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 类型。