Skip to main content
Real <table> markup is the only correct shape for tabular data. The engine gives it column tracks (header and body cells align without manual width math), a repeated <thead> on every page a long table spans, and break-aware row layout. A flex-and-div imitation gets none of that.

Canonical pattern

Data lives in a typed const at the top of the file; row JSX lives in the .map body. This is the data-rows shape from Documents.

Rules

  • Numeric columns right-align (text-right), including their <th>. Money gets fixed decimals via toLocaleString so digits line up.
  • align-top on cells when any column can wrap to two lines.
  • Two-line cells (title plus muted detail) are a nested <div tw="flex flex-col"> inside the <td>. Keep the detail line short.
  • Keep rows short: one title line plus one detail line. A row that can’t fit twice on a page is a layout smell.
  • Tables are for scannable data. Paragraphs of text belong in body copy, not cells.

Totals

Totals do not go in the table. A totals row inside <tbody> can strand alone on the next page. Close the table, then render a right-aligned summary block. breakInside: 'avoid' is reliable on that block:

Engine quirks

Two known engine behaviors to design around, not fight:
  • No explicit widths on <th>. tw="w-[45%]" on a header cell leaves an unpainted notch in the header row’s background fill. Column tracks size themselves from content; steer them by controlling cell content, not header widths. A hairline seam can still appear at a column boundary even without widths. It is a cosmetic upstream paint quirk; don’t fight it with markup.
  • breakInside: 'avoid' on <tr> is unreliable when the table sits inside a flex ancestor. A tall row can still split across pages. Keep row content short instead of building keep-together logic around rows. On ordinary block elements the declaration is reliable, see Pagination.

Anti-patterns

  • Flex rows pretending to be a table. Headers drift from body columns and nothing repeats across pages.
  • w-[...] on <th>, or hand-balancing column widths at all.
  • Left-aligned numbers, or floating-precision money ($49.9).
  • A totals row inside <tbody>.
  • Giant cells with paragraphs of text.