Universe

A Typst package for creating clean academic tables with built-in publisher format presets.

Akatable wraps Typst’s native table in a single academic-table function that automatically handles the three-line rule pattern (top, header-bottom, bottom) used across scientific publishing, along with proper figure captioning. Choose a format preset and the stroke weights and caption style match the target publisher’s guidelines.

Quick start

#import "@preview/akatable:0.1.0": academic-table

#academic-table(
  [Population of major cities],
  header: ([City], [Country], [Pop. (M)]),
  (
    [Tokyo],     [Japan],  [13.96],
    [Delhi],     [India],  [11.03],
    [Shanghai],  [China],  [24.87],
  ),
  columns: 3,
)

This produces a booktabs-style table by default: heavy top and bottom rules, a lighter mid-rule below the header, caption above.

API reference

academic-table

#academic-table(
  caption,          // content – table caption (required, positional)
  cells,            // array – body cell contents (required, positional)
  format: "apa", // string – format preset name
  header: (),       // array – header cell contents (recommended)
  footer: (),       // array – footer cell contents
  label: none,      // label – for cross-referencing
  ..args            // any extra arguments forwarded to Typst's table()
)
Parameter Type Default Description
caption content (required) The table caption, placed above the table inside a figure.
cells array (required) Flat array of cell contents. Supports table.cell(), table.hline(), and plain content.
format string "booktabs" One of the 8 built-in format presets (see below).
header array () Header cells. Supports table.cell(colspan: ..), table.cell(rowspan: ..), and table.hline() for multi-row headers.
footer array () Footer cells. Rendered below the body with a separating rule.
label label none Typst label for cross-referencing (e.g. <tab:results>).
..args Any other named argument is passed through to the underlying table(). Most commonly: columns, align, inset, row-gutter.

How the three-line rules work

Akatable uses table.hline() elements injected around the header — no stroke callback needed. This means multi-row headers (with colspan/rowspan) work correctly out of the box:

  • Top rule — placed at the start of table.header()
  • Mid rule — placed at the end of table.header(), auto-attaches after the last header row
  • Bottom rule — placed after the last body cell (or around the footer)

Format presets

Use format: "name" to switch between publisher styles. Each preset controls rule stroke weights and caption formatting.

Stroke weights

Format Top rule Mid rule Bottom rule Family
booktabs 1.5pt 0.75pt 1.5pt heavy/light/heavy
apa 1pt 1pt 1pt uniform
ieee 1.5pt 0.75pt 1.5pt heavy/light/heavy
acs 1.5pt 0.75pt 1.5pt heavy/light/heavy
nature 1.5pt 0.75pt 1.5pt heavy/light/heavy
elsevier 1.5pt 0.75pt 1.5pt heavy/light/heavy
chicago 1pt 1pt 1pt uniform
acm 1.5pt 0.75pt 1.5pt heavy/light/heavy

Caption formatting

Format Label example Title style Alignment
booktabs Table 1: plain left
apa Table 1 italic (new line) left
ieee TABLE I ALL CAPS (new line) center
acs Table 1. plain left
nature Table 1 | plain left
elsevier Table 1. plain left
chicago Table 1. plain left
acm Table 1. plain left

Usage

// APA style
#academic-table(
  [Descriptive statistics by group],
  header: ([Variable], [M], [SD]),
  ([Age], [34.2], [5.1], [Income], [52,000], [12,300]),
  format: "apa",
  columns: 3,
)

// IEEE style
#academic-table(
  [Comparison of classification accuracy],
  header: ([Method], [Precision], [Recall], [F1]),
  ([SVM], [0.92], [0.89], [0.90], [Random Forest], [0.95], [0.93], [0.94]),
  format: "ieee",
  columns: 4,
)

Advanced examples

Multi-level headers

Use table.cell(colspan: ..) and table.cell(rowspan: ..) inside the header array to create grouped column headers:

#academic-table(
  [Clinical outcomes by treatment group],
  header: (
    table.cell(rowspan: 2)[Metric],
    table.cell(colspan: 2, align: center)[Treatment A],
    table.cell(colspan: 2, align: center)[Treatment B],
    table.hline(),
    [Before], [After], [Before], [After],
  ),
  (
    [Weight (kg)], [72.1], [68.3], [71.8], [70.2],
    [BMI],         [24.5], [22.1], [24.3], [23.8],
  ),
  columns: 5,
)

Grouped rows with subtotals

Use table.hline() and table.cell(colspan: ..) inside the cells array for visual grouping:

#academic-table(
  [Project budget by category],
  header: ([Category], [Item], [Year 1], [Year 2]),
  (
    [Personnel], [Salaries],  [45,000], [46,000],
    [],          [Benefits],  [12,000], [12,500],
    table.hline(stroke: 0.5pt),
    table.cell(colspan: 2, align: right)[*Subtotal*], [*57,000*], [*58,500*],
    table.hline(stroke: 0.5pt),
    [Equipment], [Hardware],  [8,000],  [2,000],
    [],          [Software],  [3,500],  [3,500],
    table.hline(stroke: 0.5pt),
    table.cell(colspan: 2, align: right)[*Subtotal*], [*11,500*], [*5,500*],
  ),
  footer: (
    table.cell(colspan: 2, align: right)[*Grand Total*], [*68,500*], [*64,000*],
  ),
  columns: (auto, 1fr, auto, auto),
)

Row grouping with rowspan

#academic-table(
  [GDP by region],
  header: ([Region], [Country], [GDP (T\$)], [Pop. (M)]),
  (
    table.cell(rowspan: 3)[Europe],
      [Germany], [4.2],  [83],
      [France],  [2.9],  [67],
      [Italy],   [2.1],  [59],
    table.cell(rowspan: 2)[Asia],
      [China],   [17.7], [1,412],
      [Japan],   [4.2],  [125],
  ),
  columns: 4,
)

Contributing

Contributions are welcome! Some ideas for future work:

  • Additional format presets (e.g. RSC, Wiley, MDPI)
  • Font size presets per format
  • Table notes/footnotes helper
  • Accessibility metadata

Feel free to open an issue or submit a pull request on GitHub.

License

MIT