Working with tables

add/del mark content — a word, a cell, a whole figure — but a table’s own shape is different: Typst’s table() has no built-in way to conditionally include or exclude an entire row or column depending on the compile mode. Getting this wrong doesn’t just look off — it can leave a row that reviewers were told was added missing from the manuscript actually submitted, or a removed row still sitting in it.

Adding a row or column

An added row or column stays in the table in both compiles, exactly like any other #add[...] — accepted new content never disappears from the clean version. No mode() check at all: wrap each cell in add[...] and include the row unconditionally, same as writing any other table row by hand.

#import "../../lib.typ": *

#set page(width: 16.6cm, height: auto, margin: 12pt)
#set text(size: 10.5pt)

#passage[
  #figure(
    table(
      columns: 2,
      [Group], [N],
      [Control], [42],
      add[Treatment], add[45],
    ),
    caption: [Sample sizes by group.],
  )
]

Clean:

Tracked:

Removing a row or column

The opposite: a genuinely removed row or column must be absent from the clean compile, and there’s no native way to hide part of a table conditionally. The fix is to build that row’s cells as a spread array that’s empty in clean and populated in tracked — ..if mode() == "clean" { () } else { (del[...], ...) } — the same mode() already used above for require-exchange-free examples, exported by lib.typ for exactly this.

#import "../../lib.typ": *

#set page(width: 16.6cm, height: auto, margin: 12pt)
#set text(size: 10.5pt)

#passage[
  #figure(
    table(
      columns: 2,
      [Group], [N],
      [Control], [42],
      ..if mode() == "clean" { () } else { (del[Treatment], del[45]) },
      [Placebo], [40],
    ),
    caption: [Sample sizes by group.],
  )
]

Clean:

Tracked:

Combining both in one table

A table can have an added row and a removed row at the same time — the two patterns above coexist without conflict, since neither changes columns:.

#import "../../lib.typ": *

#set page(width: 16.6cm, height: auto, margin: 12pt)
#set text(size: 10.5pt)

#passage[
  #figure(
    table(
      columns: 2,
      [Group], [N],
      [Control], [42],
      ..if mode() == "clean" { () } else { (del[Placebo], del[40]) },
      add[Treatment], add[45],
    ),
    caption: [Sample sizes by group --- Placebo dropped, Treatment added.],
  )
]

Clean:

Tracked:

Columns are the trap
columns: is one fixed count for the whole table, so an added column and a removed column do not belong in the same delta: an added column is part of the fixed base count (present in both compiles, like the row case above), and only a removed column belongs in a mode-dependent addition to that count — base + int(mode() != "clean") per removed column, never per added one. Writing it the other way around silently drops the added column from the clean, submitted manuscript — easy to miss, since manuscript-tracked.pdf still looks right.
#import "../../lib.typ": *

#set page(width: 16.6cm, height: auto, margin: 12pt)
#set text(size: 10.5pt)

// Only the removed column enters the mode-dependent delta --- the
// added column is part of the fixed base count, present in both
// compiles, same as the added row above.
#passage[
  #figure(
    table(
      columns: 3 + int(mode() != "clean"),
      [Group], [N], add[p-value], ..if mode() == "clean" { () } else { (del[Old score],) },
      [Control], [42], add[0.03], ..if mode() == "clean" { () } else { (del[--],) },
    ),
    caption: [Sample sizes --- p-value column added, Old score column dropped.],
  )
]

Clean:

Tracked:

← change-listBibliography →