Typst 表格怎么做:三线表、列宽、合并单元格与 CSV 数据

Typst 表格的常用写法:table 的 columns 与 auto、fr、固定列宽,align 对齐,stroke 与 table.hline 画三线表,figure 加表题和 @tab 引用,table.cell 合并单元格,跨页长表格与重复表头,隔行填色,以及用 csv() 从文件读取数据,每个示例都能直接打开编译。

最后核对:2026-10-24

Typst 的表格和 LaTeX 一样分两层:table 排出表格本身,figure 给它加标题和编号。不同的是,列格式、线条、对齐、填充都是 table(...) 的参数,不需要额外的宏包。本文和 LaTeX 表格一文的内容一一对应,可以对照着看。在 JointTex 里,Typst 项目边输入边编译,不用点编译按钮;点 PDF 里的表格能跳到对应的源码,反过来也可以。

最基本的表格怎么写

// title: Typst 表格入门
#set text(font: ("Libertinus Serif", "Noto Serif CJK SC"), lang: "zh", region: "cn")
#set heading(numbering: "1.")

= 最简单的表格

// columns 写一个整数,表示有几列;单元格按顺序从左到右、从上到下填入
// 默认每个单元格四周都有 1pt 的黑色线条
#table(
  columns: 3,
  [姓名], [性别], [成绩],
  [张三], [男], [92],
  [李四], [女], [88],
)

= 列宽和对齐

// columns 写成数组时,每一项是一列的宽度:
// auto 按内容定宽,1fr 和 2fr 按 1:2 分掉剩余宽度,3cm 是固定宽度
// align 也可以写成数组,一列一个对齐方式
#table(
  columns: (auto, 1fr, 2fr, 3cm),
  align: (center, left, left, right),
  [编号], [名称], [说明], [数值],
  [1], [甲], [按 1fr 分到三分之一的剩余宽度], [10.5],
  [2], [乙], [按 2fr 分到三分之二,文字太长时在列内自动换行], [123.25],
)

= 带标题、可引用的表格

// 表题放在表格上方:只对 kind 为 table 的 figure 生效
#show figure.where(kind: table): set figure.caption(position: top)

如@tab:score 所示,两位同学的成绩都在 85 分以上。

// figure 负责标题和编号,table 负责表格本身;标签写在 figure 后面
#figure(
  table(
    columns: 3,
    [姓名], [数学], [英语],
    [张三], [92], [85],
    [李四], [88], [90],
  ),
  caption: [期末成绩],
) <tab:score>
Typst 表格入门在 JointTex 中打开

table 里带名字的参数(columns:、align: 等)设置格式,不带名字的参数就是单元格,从左到右、从上到下依次填入。

  • 每个单元格是一个内容块 [...],单元格之间用逗号分隔。不用 \\ 结束一行,填满 columns 指定的列数就自动换行。
  • columns: 3 表示三列,宽度由内容决定。写成数组时,数组里每一项是一列的宽度:auto 按内容定宽;1fr、2fr 按比例分掉剩下的宽度(fr 是 fraction,份数);3cm、20% 是固定宽度。
  • fr 列和 LaTeX 的 p{...} 列、tabularx 的 X 列作用相同:文字超出列宽时在列内自动换行。只要有一列是 fr,表格就正好占满整行,不会伸出页边。
  • align 写一个值对所有单元格生效,写成数组则一列一个:left、center、right,还能和垂直方向组合,如 center + horizon(水平、垂直都居中)。

默认情况下,每个单元格四周都有 1pt 的黑线,内边距是 5pt。

表格标题和引用怎么加

把 table 放进 figure,用 caption: 写标题,在 figure(...) 的右括号后面写标签 <tab:score>,正文里用 @tab:score 引用。

  • Typst 默认把标题放在下方,图和表都一样。中文论文的习惯是表题在表格上方,所以在文档开头加一行:

    #show figure.where(kind: table): set figure.caption(position: top)
    

    figure.where(kind: table) 只选中装着表格的 figure,图片的标题仍在下方。

  • figure 看得出里面是表格,表格和图片分开编号。

  • 文档设置了 #set text(lang: "zh") 时,表题前缀是"表",编号和标题之间空一个字宽(em 空格);引用 @tab:score 得到"表 1"。不设 lang 时是英文的"Table 1: 期末成绩",引用得到"Table 1"。

  • 标签必须贴在 figure 上。把标签直接写在 table 后面再引用,会报错:

    error: cannot reference table directly, try putting it into a figure
    
  • 标签名不存在时,报 error: label `<tab:none>` does not exist in the document。

  • @ 后面要留空格,或者紧跟中文标点(如",""。")。 标签名里可以有汉字,如@tab:score所示 会被读成引用 <tab:score所示>,报上面那个"不存在"的错误。写成 如@tab:score 所示,这个空格排出来就是数字和汉字之间正常的间距。

  • @ 前面不要加空格。 正文 @tab:score 里的空格会原样排出来。

和 LaTeX 不同,figure 默认不浮动,表格就排在源码所在的位置。需要浮动时给 figure 加 placement 参数,见 Typst 插图。

论文里的三线表怎么做

先用 stroke: none 去掉默认的全部线条,再用 table.hline 画横线:它写在哪两行单元格之间,线就画在哪里,stroke 参数控制粗细。

// title: Typst 三线表
#set text(font: ("Libertinus Serif", "Noto Serif CJK SC"), lang: "zh", region: "cn")

// 表题放在表格上方
#show figure.where(kind: table): set figure.caption(position: top)

@tab:result 是论文里常见的三线表:顶线和底线粗,栏目线细,没有竖线。

#figure(
  table(
    columns: 4,
    // 去掉默认的全部线条,下面只画需要的三条
    stroke: none,
    // 第一列左对齐,其余列居中
    align: (left, center, center, center),
    table.hline(stroke: 1.5pt),          // 顶线
    // table.header 标出表头:表格跨页时,表头会在新的一页重复
    table.header([方法], [准确率(%)], [召回率(%)], [耗时(s)]),
    table.hline(stroke: 0.75pt),         // 栏目线
    [方法 A], [91.2], [88.5], [12.3],
    [方法 B], [93.6], [90.1], [15.8],
    // 中文字体有真正的粗体(Noto Serif CJK SC Bold),*…* 可以直接加粗
    [本文方法], [*95.4*], [*92.7*], [13.1],
    table.hline(stroke: 1.5pt),          // 底线
  ),
  caption: [不同方法的实验结果],
) <tab:result>

// 分组表头:table.cell(colspan: 2) 横跨两列,
// table.hline(start: 1, end: 3) 只在第 2、3 列下面画线(列号从 0 开始,end 不含)
#figure(
  table(
    columns: 5,
    stroke: none,
    align: center,
    // 第 3、4 列之间留 1em 空隙,两条分组细线就不会连成一条
    column-gutter: (0pt, 0pt, 1em, 0pt),
    table.hline(stroke: 1.5pt),
    table.header(
      [], table.cell(colspan: 2)[训练集], table.cell(colspan: 2)[测试集],
      table.hline(start: 1, end: 3, stroke: 0.5pt),
      table.hline(start: 3, end: 5, stroke: 0.5pt),
      [模型], [准确率], [损失], [准确率], [损失],
    ),
    table.hline(stroke: 0.75pt),
    [基线], [0.92], [0.21], [0.88], [0.30],
    [改进], [0.95], [0.15], [0.91], [0.24],
    table.hline(stroke: 1.5pt),
  ),
  caption: [分组表头],
) <tab:group>

@tab:group 的两组表头下面各有一条细线。
Typst 三线表在 JointTex 中打开

几个要点:

  • 顶线、底线用粗一些的线(示例用 1.5pt),表头下面的栏目线用细线(0.75pt)。
  • table.header(...) 把表头的单元格包起来。它不改变外观,但表格跨页时,Typst 会在新的一页重复表头(见下文)。
  • 只在部分列下面画线用 table.hline(start: 1, end: 3),相当于 booktabs 的 \cmidrule{2-3}:列号从 0 开始,含 start、不含 end,这里画在第 2、3 列下面。
  • 两组分组线紧挨着时会连成一条。hline 没有 \cmidrule(lr) 那样的两端缩进,示例用 column-gutter: (0pt, 0pt, 1em, 0pt) 在第 3、4 列之间留出空隙:贯穿整行的线跨过空隙,部分列下面的线不跨,两条分组线就断开了。
  • 单位、百分号直接写在表头里。Typst 的标记里 % 不是特殊字符,不需要转义。

线条和底色还能怎么设置

  • stroke 写成字典:stroke: (x: none, y: 0.5pt) 去掉竖线、保留横线,x 指竖线,y 指横线。
  • stroke 和 fill 都可以写成函数,参数是单元格的列号 x 和行号 y(从 0 开始)。fill: (x, y) => if calc.even(y) { luma(235) } 让偶数行填浅灰色,就是隔行填色(斑马纹),见下文的长表格示例;函数没有返回值的行不填色。
  • 单个单元格用 table.cell(fill: ..., stroke: ...) 单独设置。

怎么合并单元格

  • 跨列:table.cell(colspan: 2)[成绩],相当于 LaTeX 的 \multicolumn。
  • 跨行:table.cell(rowspan: 2)[类别],相当于 multirow 宏包的 \multirow,不需要加载任何东西。
  • 被合并占用的位置不用再写任何东西,也不用留空的单元格。这一点和 LaTeX 相反:LaTeX 里被 \multirow 占用的格子要写空,& 不能少;Typst 里多写一个空格子,后面的内容就会全部错后一格。
  • table.cell 也可以单独改某一格的对齐方式,如 table.cell(colspan: 2, align: right)[平均]。
  • 合并后超出列数会报错:error: cell's colspan would cause it to exceed the available column(s)。
// title: Typst 合并单元格
#set text(font: ("Libertinus Serif", "Noto Serif CJK SC"), lang: "zh", region: "cn")
#show figure.where(kind: table): set figure.caption(position: top)

// table.cell(colspan: n) 横跨 n 列,table.cell(rowspan: n) 纵跨 n 行。
// 被合并占用的位置不用再写内容,后面的单元格会自动跳过它们。
#figure(
  table(
    columns: 4,
    align: center + horizon,     // 水平居中,垂直方向也居中
    table.header(
      table.cell(rowspan: 2)[类别],
      table.cell(rowspan: 2)[样本],
      table.cell(colspan: 2)[成绩],
      // 第二行表头只剩“成绩”下面的两格
      [期中], [期末],
    ),
    table.cell(rowspan: 2)[甲组], [1], [85], [88],
    // 上一行的“甲组”占了这一行的第一格,所以这里只写三格
    [2], [90], [92],
    table.cell(rowspan: 2)[乙组], [3], [78], [80],
    [4], [82], [86],
    // 跨列的单元格也可以单独改对齐方式
    table.cell(colspan: 2, align: right)[平均], [83.75], [86.5],
  ),
  caption: [分组成绩],
) <tab:merge>

@tab:merge 中,类别和样本两格各纵跨两行,成绩一格横跨两列。
Typst 合并单元格在 JointTex 中打开

表格太长、要跨页怎么办

table 本身能跨页,但放进 figure 后就不能了:figure 默认是一个不能拆开的整体,当前页放不下时,会整个挪到下一页,并在原处留下空白;比一整页还长时,会伸出页面底部。让表格的 figure 可以拆开:

#show figure.where(kind: table): set block(breakable: true)
// title: Typst 跨页长表格
#set text(font: ("Libertinus Serif", "Noto Serif CJK SC"), lang: "zh", region: "cn")
// 页面设得小一些,表格很快就会跨页
#set page(paper: "a5")
#show figure.where(kind: table): set figure.caption(position: top)

// figure 默认是一个不能拆开的整体:放不下时会整个挪到下一页。
// 让表格的 figure 可以跨页:
#show figure.where(kind: table): set block(breakable: true)

@tab:long 共 40 行,表头在每一页的顶部重复,隔行填充浅灰色。

#figure(
  table(
    columns: (auto, 1fr, auto),
    stroke: none,
    align: (center, left, right),
    // fill 写成函数,参数是列号 x 和行号 y(都从 0 开始);
    // 表头是第 0 行,数据从第 1 行开始,偶数行填浅灰色
    fill: (x, y) => if y > 0 and calc.even(y) { luma(235) },
    // table.header 默认 repeat: true,跨页后在新一页重复
    table.header(
      table.hline(stroke: 1.5pt),
      [编号], [名称], [数值],
      table.hline(stroke: 0.75pt),
    ),
    // 用循环生成 40 行数据:每次产生三个单元格
    ..for i in range(1, 41) {
      ([#i], [样品 #i], [#(i * 12.5)])
    },
    table.hline(stroke: 1.5pt),
  ),
  caption: [长表格示例],
) <tab:long>
Typst 跨页长表格在 JointTex 中打开
  • 用 table.header 包起来的表头会在每一页的顶部重复,写在它里面的 table.hline 也一起重复,所以续页也有顶线。不想重复时写 table.header(repeat: false, ...)。
  • 放在表格最后的 table.hline 不只画在最后一页:表格在每一页断开的地方都会画出这条线,每一页的表格都是封闭的三线表。
  • 需要每一页底部都出现的内容(如合计行),可以用 table.footer(...),它同样默认在每一页重复。

不需要像 LaTeX 那样换用 longtable 环境,也不需要多编译一次。

表格数据能不能从文件读

能。csv("data.csv") 读入项目里的 CSV 文件,得到一个数组:每一项是一行,每一行又是一个字符串数组。改了数据,表格跟着变。

// title: Typst 从 CSV 读取表格
#set text(font: ("Libertinus Serif", "Noto Serif CJK SC"), lang: "zh", region: "cn")
#show figure.where(kind: table): set figure.caption(position: top)

// csv() 读入项目里的 data.csv,得到一个数组,每一项是一行,
// 每一行又是一个字符串数组。第一行是表头。
#let rows = csv("data.csv")
#let head = rows.first()   // 第一行:表头
#let body = rows.slice(1)  // 其余各行:数据

@tab:sample 的数据直接来自 data.csv,改动 CSV 文件后表格跟着变。

#figure(
  table(
    // 列数取表头的长度,不必手写
    columns: head.len(),
    stroke: none,
    align: (left, right, right),
    table.hline(stroke: 1.5pt),
    // .. 把数组展开成多个参数;map 把字符串变成单元格内容
    table.header(..head.map(h => [#h])),
    table.hline(stroke: 0.75pt),
    // flatten 把“行的数组”摊平成一串单元格
    ..body.flatten(),
    table.hline(stroke: 1.5pt),
  ),
  caption: [各样品的测量结果],
) <tab:sample>
Typst 从 CSV 读取表格在 JointTex 中打开
  • rows.first() 取第一行做表头,rows.slice(1) 取其余各行,.flatten() 把"行的数组"摊平成一串单元格交给 table。

  • 用分号分隔的文件写 csv("data.csv", delimiter: ";")。

  • 文件必须是 UTF-8 编码。在中文版 Excel 里直接"另存为 CSV",得到的常常是 GBK 编码,会报:

    error: failed to parse CSV (file is not valid UTF-8 in data.csv:1:1)
    

    另存时选择"CSV UTF-8"格式即可。

  • 某一行的列数和第一行不同时,报错会指出行号:error: failed to parse CSV (found 3 instead of 2 fields in line 2)。

字号、内边距和表题格式怎么改

  • 表格字号小一号:#show table: set text(size: 9pt),对它后面的所有表格生效。只改一个表格时,把这一行和那个表格一起放进 #[ ... ],规则只在这一块里起作用。
  • 行距太挤或太松:调单元格的内边距,如 #set table(inset: (x: 8pt, y: 4pt)),x 是左右,y 是上下。
  • 表头加粗:#show table.cell.where(y: 0): set text(weight: "bold")。Typst 不会给没有粗体字形的中文字体"伪造"粗体;示例用的 Noto Serif CJK SC 有粗体,能正常加粗。
  • 表题改成"表 1:标题"、字号小一号:#set figure.caption(separator: [:]) 和 #show figure.caption: set text(size: 9pt),对图题和表题都生效。只改表题时,在 show figure.where(kind: table): 后面写 set figure.caption(separator: [:])。

常见错误

  • 单元格之间漏了逗号:[a] [b] 报 error: expected comma。
  • 单元格个数不是列数的整数倍:不报错,最后一行缺的格子留空,表格错位时先数一数单元格。
  • 单元格里的 //:// 在 Typst 里是注释,[a // b] 会把这一行后面的内容(包括 ])都当成注释,报 error: unclosed delimiter。要写两个斜杠时用 \/\/。以 https:// 开头的网址不受影响,Typst 会把它识别为链接。
  • 单元格里的 @:邮箱 [email protected] 会被当成引用,报 error: label `<b.com>` does not exist in the document,写成 a\@b.com。

报错在 JointTex 里会在源码对应位置画上波浪线,并列在"问题"标签页中。更多报错见 Typst 常见报错。表格里的中文字体设置见 Typst 中文排版。

本文示例只用了编辑器页面自带的字体(Libertinus Serif 和 Noto Serif CJK SC)。项目在 Server 上编译时还有 Noto Sans CJK 等其他中文字体,也可以把字体文件上传到项目的 fonts/ 文件夹。每个示例都可以在 JointTex 中一键打开为新项目,改成自己的表格。

JointTex

Browser compatibility

JointTex could not start

The application did not finish loading. Check your connection, then try again.

Supported versions

  • Chrome and Edge 88 or later
  • Firefox 84 or later
  • Safari and iOS Safari 14 or later
Reload