Is it possible to give a table different-style first header, continuing header, continuing footer and final footer, as done by the LaTeX package longtable?
I find solutions that would work to make the first header different, e.g., https://forum.typst.app/t/page-x-of-y-as-part-of-a-table-header/6685, but how can a footer know whether it is the last?
There’s an example of continuation header and footer here (taken from here), I hope that makes it clear how it can be done. We compare the current counter value to the final value to know if we’re on the final footer:
// This is an example of a table
// with continuation headers and footers
//
// See also these links:
// - https://discord.com/channels/1054443721975922748/1221887107350396999
// - https://github.com/typst/typst/issues/735
//
#let table-counter = counter("_table_continuation")
/// This style rule needs to be applied so that we have separate counters per table
#let table-continuation-style(body) = {
show table: it => table-counter.step() + it
body
}
#let headcounter() = counter("_table_continuation_header:" + str(table-counter.get().first()))
#let footcounter() = counter("_table_continuation_footer:" + str(table-counter.get().first()))
#let continuation-header(first, cont, inset: auto, ..args) = {
let cellargs = args.named()
if inset != auto { cellargs += (inset: inset) }
table.header(table.cell(..cellargs, context {
let counter = headcounter()
counter.step()
let current = 1 + counter.get().first()
if current == 1 {
first
} else {
cont
}
}))
}
#let continuation-footer(cont, inset: 0pt, last: none, ..args) = {
// Need to make the cell "invisible" / take no space when not used, so inset is 0pt by default
let cellargs = args.named()
if inset != auto { cellargs += (inset: inset) }
table.footer(table.cell(..cellargs, context {
let counter = footcounter()
counter.step()
let current = 1 + counter.get().first()
let final = counter.final().first()
if current < final {
cont
} else {
last
}
}))
}
// BEGIN Example Document
#set page(margin: 1cm, width: 10cm, height: 6cm)
#set document(date: none)
#show: table-continuation-style
#show math.equation: set block(breakable: true)
#table(
stroke: 0.2pt,
continuation-header([Example], [Example (cont'd)]),
{
$
[ sqrt(2)(cos pi/8 + i sin pi/8)]^12: \
[ sqrt(2)(cos pi/8 + i sin pi/8)]^12 = \
// just filler here
= [ sqrt(2)(cos pi/8 + i sin pi/8)]^12 \
= [ sqrt(2)(cos pi/8 + i sin pi/8)]^12 \
= [ sqrt(2)(cos pi/8 + i sin pi/8)]^12 \
$
},
continuation-footer[#align(right, box(inset: 5pt)[continued $->$])]
)
// for layout convergence there is a pagebreak separating the two examples.
// Yes, conditionally inserting a footer can cause convergence problems. As a
// last resort it can also be avoided by making sure the continuation footer
// always has the same size, even in the last (empty) instance.
#pagebreak()
Note the convergence comment in the end of the code - in my experience it’s a bit fragile to layout convergence problems. If the two cases of footer are the same size, it should not be a problem.
@bluss already gave the solution, but I had the idea to wrap a second table around the first table. It doesn’t work because both headers appear at the top and both footers at the bottom. Possibly this could be made to work but I don’t see it having any improvements over the existing answer.
Code and output if you're interested
#set page(height: 4cm)
#set page(width: auto)
#table(
columns: 1,
row-gutter: 0mm,
stroke: red,
[Outer header],
table.cell(
inset: 0mm,
table(
columns: 1,
stroke: 0.5pt + blue,
table.header("Inner Header"),
..range(6).map(str),
table.footer("Inner Footer")
)
),
[Outer footer]
)
Awesome! It now even quite easy to realise what I was trying here in LaTeX ![]()
Let’s now how this issue will be solved…

