Timeline 时间线

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

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

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',
  },
]

export default function Demo() {
  return <Timeline items={items} className="max-w-lg" />
}
tsx

用法

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

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

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

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',
  },
]

export default function Demo() {
  return <Timeline items={items} />
}
tsx

示例

节点颜色

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

  1. accent
  2. neutral
  3. success
  4. warning
  5. danger
  6. info
import { Timeline, type TimelineTone } from '@hina-ui/react'

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

export default function Demo() {
  return <Timeline items={items} />
}
tsx

对齐方式

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

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

import { useState } from 'react'
import { Timeline, Select, Stack, type TimelineAlign } from '@hina-ui/react'

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',
  },
]

export default function Demo() {
  const [align, setAlign] = useState<TimelineAlign>('alternate')

  return (
    <Stack className="w-full" gap="lg">
      <Select
        value={align}
        onValueChange={value => setAlign(value as TimelineAlign)}
        options={options}
        aria-label="对齐方式"
        className="w-40"
      />
      <Timeline items={items} align={align} />
    </Stack>
  )
}
tsx

对侧时间

timePosition="opposite" 将时间移到轴的另一侧,纵向两侧等宽。renderOpposite 可替换对侧区域;提供它时会预留对侧空间。

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

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

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',
  },
]

export default function Demo() {
  return <Timeline items={items} timePosition="opposite" />
}
tsx

横向

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

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

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

import { useState } from 'react'
import { Timeline, Select, Stack, ScrollArea, type TimelineAlign } from '@hina-ui/react'

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',
  },
]

export default function Demo() {
  const [align, setAlign] = useState<TimelineAlign>('start')

  return (
    <Stack className="w-full" gap="lg">
      <Select
        value={align}
        onValueChange={value => setAlign(value as TimelineAlign)}
        options={options}
        aria-label="对齐方式"
        className="w-40"
      />
      <ScrollArea direction="horizontal" shadow={false} className="w-full">
        <Timeline items={items} orientation="horizontal" align={align} className="min-w-lg" />
      </ScrollArea>
    </Stack>
  )
}
tsx

尺寸

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

sm

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

md

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

lg

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

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',
  },
]

export default function Demo() {
  return (
    <SimpleGrid min="12rem" className="w-full" gap="lg">
      {sizes.map(size => (
        <Stack key={size}>
          <Text size="sm" tone="muted">
            {size}
          </Text>
          <Timeline items={items} size={size} />
        </Stack>
      ))}
    </SimpleGrid>
  )
}
tsx

自定义节点与内容

renderMarker 替换节点,可组合 Avatar、Spinner 或图标。renderContent 替换整个正文区域,包括默认时间、标题和描述;需要只改某一部分时,用 renderTime、renderTitle 或 renderDescription。

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

  1. 已完成

    完成

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

    Hina

  2. 已补充

    完成

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

    Hina

  3. 进行中

    进行中

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

    Hina

'use client'

import { Check } from 'lucide-react'
import {
  Avatar,
  Card,
  Inline,
  Spinner,
  Stack,
  Tag,
  Text,
  Time,
  Timeline,
  type TimelineItem,
} from '@hina-ui/react'

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',
  },
]

export default function Demo() {
  return (
    <Timeline
      items={items}
      className="max-w-xl"
      renderMarker={({ item, index }) =>
        index === 0 ? (
          <Avatar name={item.author} size="sm" />
        ) : item.pending ? (
          <Spinner size="sm" />
        ) : (
          <Check />
        )
      }
      renderContent={({ 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" className="text-muted text-xs" />
            </Inline>
          </Stack>
        </Card>
      )}
    />
  )
}
tsx

反向顺序

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

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

import { useState } from 'react'
import { Stack, Switch, Timeline } from '@hina-ui/react'

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',
  },
]

export default function Demo() {
  const [reverse, setReverse] = useState(false)

  return (
    <Stack className="w-full" gap="lg">
      <Switch checked={reverse} onCheckedChange={setReverse}>
        反向排列
      </Switch>
      <Timeline items={items} reverse={reverse} />
    </Stack>
  )
}
tsx

RTL

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

  1. 09:00
    البند الأول
    وصف موجز للبند الأول.
  2. 09:20
    البند الثاني
    وصف البند الثاني.
  3. 09:40
    البند الثالث
import { Timeline } from '@hina-ui/react'

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

export default function Demo() {
  return <Timeline items={items} dir="rtl" />
}
tsx

无障碍

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

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
反转显示顺序
className
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'。所有条目字段均可选,渲染函数可以完全接管呈现。

内容属性

属性
参数
说明
renderMarker
{ item: T, index: number }
节点装饰,默认空心圆点
renderContent
{ item: T, index: number }
整个正文区域
renderTitle
{ item: T, index: number }
标题
renderDescription
{ item: T, index: number }
描述
renderTime
{ item: T, index: number }
时间,位置由 timePosition 决定
renderOpposite
{ item: T, index: number }
对侧区域,优先于对侧时间

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