Skip to content

[Bug] presep is ignored / gap collapses when longtblr (not \Needspace*) is what forces the table+preceding heading onto a new page #674

Description

@artcdi2

Tell us about the system on which the bug was produced

  • Distribution and year: TeX Live 2025
  • Engine used: pdfLaTeX (also affects LuaLaTeX — same mechanism, not engine-specific)
  • Operating system: Windows 11
  • tabularray version: 2025-11-27 (release 2025C)

Please outline the bug as you have found it

When a longtblr table immediately follows a \section heading, and both
a manual widow-heading guard (\Needspace* before \section, a very
common pattern — see the needspace package docs, or e.g. the widely
cited comp.text.tex thread "Section headings and longtables")
and longtblr's own page-break logic are both in play, the vertical gap
between the heading and the table's top border depends on which of the
two mechanisms actually triggered the page break
— even though presep
and the row's abovesep/belowsep are identical in both cases.

  • If \Needspace* triggers by itself (not enough room left for the
    heading alone, decided before longtblr is even reached), the heading
    and table both start fresh on the new page and the gap is correct
    (presep is honored as expected).
  • If \Needspace* would have passed on its own (enough room left for the
    heading), but the table's header + first row don't fit in what remains
    afterwards, it's longtblr's own mechanism that forces the break —
    dragging the heading along with it, so it stays glued to the top of the
    table rather than being left as a widow (this happens because LaTeX's
    page builder retroactively picks the earlier feasible breakpoint via
    penalties — visually indistinguishable from the first case: the heading
    simply appears alone at the top of the new page in both). In this case
    the gap collapses to roughly 1/7 of its expected size (in the MWE
    below: ≈58px vs ≈4px at 300dpi, i.e. from a correct ≈14pt gap down to
    under 1pt), with the exact same presep=6pt and abovesep=80pt on
    the header row in both cases.

We suspect this may be related to \LT@setprevdepth (inherited from
longtable.dtx), which resets \prevdepth to 0pt at the start of every
new page chunk. That's presumably intentional for longtable's own
continuation-chunk logic, but combined with tabularray's row-level
abovesep, the computed vertical offset of the first row on a
"chunk-start" page seems to become increasingly wrong as abovesep
grows — apparently regardless of presep. With a large enough abovesep
(80pt in the MWE) it goes far enough that the table's top border nearly
touches/overlaps the heading text. We have not dug into tabularray's
internals to confirm this is the actual mechanism — flagging it as a
hypothesis.

The effect scales with abovesep/belowsep on the header row: at
moderate values (≤40pt in our testing) the discrepancy is small enough to
be easy to miss; it becomes visually dramatic starting around 60pt+. This
may be why it hasn't been reported before — most tables don't use such
large explicit abovesep, but a tall, multi-line wrapped header can
plausibly reach an equivalent effective height and trigger the same bug
without any unusual options.

Share a minimum working example to reproduce the bug

\documentclass[a4paper,14pt]{extarticle}
\usepackage{geometry}
\geometry{a4paper,top=2cm,bottom=2cm,left=3cm,right=1.5cm}
\usepackage{etoolbox}
\usepackage{needspace}
\usepackage{tabularray}
\UseTblrLibrary{booktabs}
\DefTblrTemplate{caption-tag}{default}{}
\DefTblrTemplate{caption-sep}{default}{}
\DefTblrTemplate{caption-text}{default}{}

\usepackage{titlesec}
% Same widow-heading protection many real documents use:
\pretocmd{\section}{\Needspace*{13\baselineskip}}{}{}
\titlespacing{\section}{0pt}{6pt}{4pt}

\newcommand{\demoTable}[1]{%
\begin{longtblr}[presep=6pt,postsep=14pt]{
  colspec = {X[l,m] *{3}{Q[c,m,2cm]}},
  hlines, vlines, rowhead=2,
  row{1,2} = {c,m,font=\bfseries\small,#1},
}
  \SetCell[r=2]{c,m} Header &
  \SetCell[c=3]{c,m} Mode & & \\
  & A & B & C \\
  Row 1 & 5 & 5 & 5 \\
  Row 2 & 64 & 32 & 16 \\
\end{longtblr}}

\begin{document}

\section{Intro}
Intro text.

% ----- Case A: \Needspace* triggers BY ITSELF (remaining < 13 lines)
%       before longtblr is even reached -> heading+table both start
%       fresh; gap is correct.
\clearpage
\vspace*{\dimexpr\textheight-6cm\relax}
\section{Case A -- needspace triggers on its own}
\demoTable{abovesep=80pt,belowsep=80pt}

% ----- Case B: remaining space is ENOUGH for \Needspace* to pass on
%       its own (>= 13 lines), but NOT enough for the table's header +
%       1 row -> longtblr's OWN mechanism forces the break, dragging
%       the heading along with it. Gap becomes ~1/7 of Case A's gap,
%       with the SAME abovesep on the header row in both cases.
\clearpage
\vspace*{\dimexpr\textheight-12cm\relax}
\section{Case B -- longtblr forces the break instead}
\demoTable{abovesep=80pt,belowsep=80pt}

\end{document}

Please share any other information which you may find helpful

Expected output: the gap between the \section heading and the
table's top border should be governed consistently by presep/
titlespacing, regardless of which mechanism (\Needspace* or
longtblr itself) ends up responsible for the page break.

Actual output:

  • Case A (page 3 of the compiled PDF): heading-to-border gap ≈58px @
    300dpi (≈14pt) — correct.
  • Case B (page 5): heading-to-border gap ≈4px @ 300dpi (≈1pt) — the
    table's top border almost touches the heading text, with identical
    presep/abovesep values to Case A.

Screenshots of both cases attached (case-a-correct.png,
case-b-broken.png).

No errors or warnings in the log; both cases compile cleanly.

Ensure you have filled out all the necessary details

  • System information
  • Outline of the bug, including MWE for reproduction
Image Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions