Material UI(@mui/lab)Timeline 时间线组件详解:从基础布局到位置系统与源码实现
本文围绕 Material UI 实验室包 @mui/lab 中的 Timeline 组件族展开,完整覆盖文档页 docs/data/material/components/timeline/timeline.md 中的全部示例场景(基础时间线、左侧定位、交替布局、颜色与轮廓变体、对侧内容、深度定制与容器内对齐),并结合 packages/mui-lab/src 下的源码实现,讲清 position 属性如何通过 Context 贯穿六个子组件、各默认值与类名规则从何而来,帮助读者既能直接复制可运行的示例,也能理解其底层布局机制。
一、Timeline 是什么,以及它属于哪个包
时间线(Timeline)组件用于按时间顺序展示一组事件(the timeline displays a list of events in chronological order)。文档中特别注明了一个事实:该组件并未出现在 Google 的 Material Design 官方指南中,但 Material UI 提供了它,并放置在实验性包中:
- 所属包:
@mui/lab(组件文档 frontmatter 中声明packageName: '@mui/lab',见 timeline.md); - 组件族包含 7 个部分:
Timeline、TimelineItem、TimelineSeparator、TimelineDot、TimelineConnector、TimelineContent、TimelineOppositeContent,源码分别位于 Timeline、TimelineItem、TimelineSeparator、TimelineDot、TimelineConnector、TimelineContent、TimelineOppositeContent。
安装后按子路径导入即可:
npm install @mui/lab @mui/material @emotion/react @emotion/styled
import Timeline from '@mui/lab/Timeline';
import TimelineItem from '@mui/lab/TimelineItem';
import TimelineSeparator from '@mui/lab/TimelineSeparator';
import TimelineConnector from '@mui/lab/TimelineConnector';
import TimelineContent from '@mui/lab/TimelineContent';
import TimelineDot from '@mui/lab/TimelineDot';
import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent';
由于 Timeline 位于 @mui/lab,从源码结构看它属于实验性 API,接口在正式版中仍可能演进,生产使用前建议关注版本说明。
二、基础时间线:最小可用结构
文档的第一节 "Basic timeline" 展示了最简单的用法(对应 BasicTimeline.tsx)。其结构规律是:Timeline(容器)→ TimelineItem(单个事件)→ TimelineSeparator(中轴分隔区)+ TimelineContent(事件内容),其中 Separator 内再嵌套 Dot 与 Connector:
import Timeline from '@mui/lab/Timeline';
import TimelineItem from '@mui/lab/TimelineItem';
import TimelineSeparator from '@mui/lab/TimelineSeparator';
import TimelineConnector from '@mui/lab/TimelineConnector';
import TimelineContent from '@mui/lab/TimelineContent';
import TimelineDot from '@mui/lab/TimelineDot';
export default function BasicTimeline() {
return (
<Timeline>
<TimelineItem>
<TimelineSeparator>
<TimelineDot />
<TimelineConnector />
</TimelineSeparator>
<TimelineContent>Eat</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineSeparator>
<TimelineDot />
<TimelineConnector />
</TimelineSeparator>
<TimelineContent>Code</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineSeparator>
<TimelineDot />
</TimelineSeparator>
<TimelineContent>Sleep</TimelineContent>
</TimelineItem>
</Timeline>
);
}
注意细节:
- 最后一个
TimelineItem不写TimelineConnector——Connector 是连接当前 Dot 与下一个 Dot 的竖线,末尾无下一条事件,故省略。 - 从 Timeline.tsx 看,
Timeline的根元素是<ul>(styled('ul', ...)),TimelineItem的根元素是<li>(styled('li', ...),见 TimelineItem.js),语义上构成一个真正的无序列表。根样式为display: flex; flex-direction: column; padding: 6px 16px; flex-grow: 1,即纵向堆叠、自身撑满父容器宽度——这是后文"默认在容器内居中"这一行为的来源。 TimelineConnector从 源码 看是一条固定 2px 宽、默认取主题palette.grey[400]背景色、并设置flexGrow: 1的竖线,因此它会自动占据 Dot 之间的剩余高度,不需要手动指定高度。
三、position 属性:内容相对时间轴的四种定位
Timeline 组件唯一的功能型属性是 position,它决定 TimelineContent 相对于中轴(时间轴)出现在哪一侧。类型定义(Timeline.types.ts)与 PropTypes(Timeline.tsx)给出取值与默认值:
| 取值 | 说明 |
|---|---|
'right' |
默认值(@default 'right'),内容显示在时间轴右侧 |
'left' |
内容显示在时间轴左侧 |
'alternate' |
内容在左右两侧交替显示,第一个事件在右 |
'alternate-reverse' |
交替显示,但顺序反转,第一个事件在左 |
3.1 左侧定位时间线
文档 "Left-positioned timeline" 小节说明:主内容可以相对时间轴定位在左侧,只需给容器加 position="left"(完整示例见 LeftPositionedTimeline.tsx):
<Timeline position="left">
{/* TimelineItem 结构同基础示例 */}
</Timeline>
源码中这一属性的作用路径是:
Timeline组件把position默认值归一化后写入 TimelineContext:const { position = 'right', ... } = props(Timeline.tsx);- 每个
TimelineItem通过React.useContext(TimelineContext)读取该值,允许单个 item 用自身position属性覆盖容器级设置,并再次向下传递(TimelineItem.js); TimelineContent与TimelineOppositeContent同样消费该 Context,并据此翻转文本对齐方式:TimelineContent在position === 'left'时textAlign: 'right',TimelineOppositeContent在position === 'left'时textAlign: 'left'(TimelineContent.js、TimelineOppositeContent.js)。
也就是说,position 是沿着 Timeline → TimelineItem → Content/OppositeContent 的 Context 链逐层流动的,任何一层缺省都会沿链向上继承,最终兜底到 'right'。
3.2 交替时间线
文档 "Alternating timeline" 与 "Reverse Alternating timeline" 两节分别演示 position="alternate" 与 position="alternate-reverse"(示例见 AlternateTimeline.tsx、AlternateReverseTimeline.tsx),让事件在左右两侧交替出现。从源码看实现非常克制,全部靠 CSS 选择器完成(TimelineItem.js):
...((ownerState.position === 'alternate' || ownerState.position === 'alternate-reverse') && {
[`&:nth-of-type(${ownerState.position === 'alternate' ? 'even' : 'odd'})`]: {
flexDirection: 'row-reverse',
[`& .${timelineContentClasses.root}`]: {
textAlign: 'right',
},
[`& .${timelineOppositeContentClasses.root}`]: {
textAlign: 'left',
},
},
}),
即:alternate 时把偶数项、alternate-reverse 时把奇数项的 flexDirection 反转为 row-reverse,并同步翻转两侧内容的对齐方向,JS 逻辑中没有任何索引运算。
3.3 position 与类名的对应关系
每个位置值都会生成一个稳定的工具类,映射函数 convertTimelinePositionToClass 把 'alternate-reverse' 特判为 positionAlternateReverse,其余按首字母大写拼接(positionLeft / positionRight / positionAlternate)。完整类名清单见 timelineClasses.ts:root、positionLeft、positionRight、positionAlternate、positionAlternateReverse。
该行为有测试保障:Timeline.test.tsx 中验证了 position="alternate-reverse" 的根元素会带有 MuiTimeline-positionAlternateReverse 类名,且组件遵循 ul 继承、ref 类型等规范。
四、TimelineDot:颜色与 variant 变体
文档 "Color" 与 "Outlined" 两节展示 Dot 的两种视觉维度,二者都由 TimelineDot 的属性控制。
4.1 颜色(color)
TimelineDot 支持取自主题调色板的颜色(示例 ColorsTimeline.tsx):
<Timeline position="alternate">
<TimelineItem>
<TimelineSeparator>
<TimelineDot color="secondary" />
<TimelineConnector />
</TimelineSeparator>
<TimelineContent>Secondary</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineSeparator>
<TimelineDot color="success" />
</TimelineSeparator>
<TimelineContent>Success</TimelineContent>
</TimelineItem>
</Timeline>
从 TimelineDot.js 的 PropTypes 看,color 默认 'grey',可选枚举值为 'error' | 'grey' | 'info' | 'inherit' | 'primary' | 'secondary' | 'success' | 'warning'(PropTypes 同时宽松接受任意字符串)。样式逻辑上:
variant="filled"(默认):取palette[color].main作为背景、palette[color].contrastText作为文字色;grey走特例(grey[500]背景 +grey[400]相关配色);color="inherit":不附加颜色相关类与内联样式,完全交给外部sx/classes控制。
4.2 轮廓变体(variant)
"Outlined" 一节(示例 OutlinedTimeline.tsx)把 variant="outlined" 与颜色组合:
<Timeline position="alternate">
<TimelineItem>
<TimelineSeparator>
<TimelineDot variant="outlined" />
<TimelineConnector />
</TimelineSeparator>
<TimelineContent>Eat</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineSeparator>
<TimelineDot variant="outlined" color="primary" />
<TimelineConnector />
</TimelineSeparator>
<TimelineContent>Code</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineSeparator>
<TimelineDot variant="outlined" color="secondary" />
<TimelineConnector />
</TimelineSeparator>
<TimelineContent>Sleep</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineSeparator>
<TimelineDot variant="outlined" />
</TimelineSeparator>
<TimelineContent>Repeat</TimelineContent>
</TimelineItem>
</Timeline>
variant 默认 'filled'。从源码看两种变体的差异:filled 有 2px 边框(borderColor: 'transparent')、圆角 50%、4px 内边距、shadows[1] 阴影;outlined 则去掉阴影(boxShadow: 'none')、背景透明,仅保留主题色边框。每个 variant + color 组合还会生成独立工具类(如 filledPrimary、outlinedSecondary,由 useUtilityClasses 中 ${variant}${capitalize(color)} 拼出),便于通过 classes 精确定制。
五、TimelineOppositeContent:对侧内容
文档 "Opposite content" 小节说明:时间线可以在中轴的另一侧显示内容,典型用途是展示时间戳(示例 OppositeContentTimeline.tsx):
import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent';
<Timeline position="alternate">
<TimelineItem>
<TimelineOppositeContent
sx={{
color: 'text.secondary',
}}
>
09:30 am
</TimelineOppositeContent>
<TimelineSeparator>
<TimelineDot />
<TimelineConnector />
</TimelineSeparator>
<TimelineContent>Eat</TimelineContent>
</TimelineItem>
{/* 其余 item 同构:09:30 / 10:00 / 12:00 / 9:00 am */}
</Timeline>
TimelineOppositeContent 同样基于 Typography(styled(Typography, ...)),默认样式为 padding: 6px 16px; margin-right: auto; text-align: right; flex: 1(TimelineOppositeContent.js)。
这里有一个值得注意的联动设计:TimelineItem 在渲染前会遍历子节点,检查是否存在 TimelineOppositeContent(用 isMuiElement(child, ['TimelineOppositeContent']) 判断,见 TimelineItem.js)。若没有对侧内容,item 会通过 ::before 伪元素插入一个 flex: 1 的占位块(missingOppositeContent 状态类),把内容区保持在中间位置:
[`&:where(:not(:has(.${timelineOppositeContentClasses.root})))::before`]: {
content: '""',
flex: 1,
padding: '6px 16px',
},
从源码注释看,该选择器特意用 :where() 压住特异性到 (0,1,1),以便用户对 ::before 的自定义覆盖能胜出——这正是下面 "Left-aligned with no opposite content" 示例成立的前提。
六、深度定制:图标、标题、时间轴染色
文档 "Customization" 一节给出官方定制示例(CustomizedTimeline.tsx),要点可归纳为四类技巧,官方文档建议配合 样式覆盖文档 进一步学习:
- 把图标放进 Dot:
TimelineDot接受 children,直接包一层@mui/icons-material图标(FastfoodIcon、LaptopMacIcon等),Dot 的圆角、内边距与阴影会自动包裹图标; - 富文本内容:
TimelineContent基于Typography,内部可放多段Typography(标题 + 描述),并用sx={{ py: '12px', px: 2 }}调整内边距; - 时间轴染色:
TimelineConnector支持sx={{ bgcolor: 'secondary.main' }}改变竖线颜色; - 对侧内容的排版:
TimelineOppositeContent可使用align、variant="body2"等 Typography 属性。
完整示例(节选一个 item):
<TimelineItem>
<TimelineOppositeContent
align="right"
variant="body2"
sx={{
color: 'text.secondary',
m: 'auto 0',
}}
>
9:30 am
</TimelineOppositeContent>
<TimelineSeparator>
<TimelineConnector />
<TimelineDot>
<FastfoodIcon />
</TimelineDot>
<TimelineConnector />
</TimelineSeparator>
<TimelineContent sx={{ py: '12px', px: 2 }}>
<Typography variant="h6" component="span">
Eat
</Typography>
<Typography>Because you need strength</Typography>
</TimelineContent>
</TimelineItem>
七、容器内对齐:利用 flex 权重调整左右比例
文档 "Alignment" 一节说明:Timeline 有多种在容器内摆放的方式,可通过样式覆盖实现;Timeline 默认在容器内居中。三个示例分别演示如何调整左右两侧的比例(示例源码 LeftAlignedTimeline.tsx、RightAlignedTimeline.tsx、NoOppositeContent.tsx)。
7.1 左对齐
压缩对侧内容(时间戳列)的 flex 权重到 0.2,主内容区自然占据剩余空间,整体视觉上"左对齐":
import TimelineOppositeContent, {
timelineOppositeContentClasses,
} from '@mui/lab/TimelineOppositeContent';
<Timeline
sx={{
[`& .${timelineOppositeContentClasses.root}`]: {
flex: 0.2,
},
}}
>
<TimelineItem>
<TimelineOppositeContent color="textSecondary">09:30 am</TimelineOppositeContent>
<TimelineSeparator>
<TimelineDot />
<TimelineConnector />
</TimelineSeparator>
<TimelineContent>Eat</TimelineContent>
</TimelineItem>
</Timeline>
7.2 右对齐
对称地,压缩主内容区(timelineContentClasses.root)的权重:
import TimelineContent, { timelineContentClasses } from '@mui/lab/TimelineContent';
<Timeline
sx={{
[`& .${timelineContentClasses.root}`]: {
flex: 0.2,
},
}}
>
{/* items 同上 */}
</Timeline>
7.3 无对侧内容时的左对齐
当 item 没有 TimelineOppositeContent 时,::before 占位伪元素(flex: 1)会把内容推到中间;此时把该伪元素的 flex 与 padding 都归零即可实现左对齐:
import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
<Timeline
sx={{
[`& .${timelineItemClasses.root}:before`]: {
flex: 0,
padding: 0,
},
}}
>
{/* 无 TimelineOppositeContent 的 items */}
</Timeline>
这类调整之所以可行,正是因为每个部分都导出了稳定的类名常量(timelineContentClasses、timelineOppositeContentClasses、timelineItemClasses 等),而 Timeline 根元素的 flexGrow: 1(Timeline.tsx)保证了整个时间线会先撑满父容器、再由内部 flex 权重分配左右比例。
八、组件结构与定制入口速查
结合源码与文档示例,各部分的职责与定制入口如下表:
| 组件 | 根元素 | 默认样式要点(源码依据) | 关键属性 |
|---|---|---|---|
Timeline |
ul |
display: flex; flex-direction: column; padding: 6px 16px; flex-grow: 1 |
position(默认 'right')、sx、classes |
TimelineItem |
li |
display: flex; min-height: 70px; position: relative;无对侧内容时 ::before 占位 |
position(可覆盖容器值)、sx |
TimelineSeparator |
div |
display: flex; flex-direction: column; align-items: center; flex: 0 |
sx(如 bgcolor 染线) |
TimelineDot |
span |
2px 边框、border-radius: 50%、padding: 4、shadows[1] |
color(默认 'grey')、variant('filled' / 'outlined')、children(图标) |
TimelineConnector |
span |
width: 2; background: palette.grey[400]; flex-grow: 1 |
sx |
TimelineContent |
div(基于 Typography) |
flex: 1; padding: 6px 16px;position="left" 时右对齐 |
variant、align 等 Typography 属性 |
TimelineOppositeContent |
div(基于 Typography) |
padding: 6px 16px; margin-right: auto; text-align: right; flex: 1 |
同上 |
定制有三个统一入口,与 Material UI 全局约定一致:
sx属性:所有七个组件都接受(源码 PropTypes 中均有sx声明);classes属性 + 各部分导出的*Classes常量:精确命中某个部分,例如上文对齐示例;- 主题
components.MuiTimeline等 overrides:通过styled组件声明的overridesResolver(如 Timeline.tsx)把position状态类并入覆盖链,因此主题层也能按位置分别覆写。
九、小结
@mui/lab 的 Timeline 是一套"少属性、强组合"的组件族:布局变化全部由容器一个 position 属性驱动,并通过 React Context 逐层下发;视觉变体集中在 TimelineDot 的 color / variant 两个属性;而更细的排版(对齐、占比、染色)则依托稳定的工具类常量与 sx 完成。配合仓库中 timeline.md 的九个官方示例(BasicTimeline、LeftPositionedTimeline、AlternateTimeline、AlternateReverseTimeline、ColorsTimeline、OutlinedTimeline、OppositeContentTimeline、CustomizedTimeline、对齐三例)与 源码实现,基本可以覆盖事件日志、部署记录、履历展示等常见时间线场景的落地需求。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00