Printing HTML tables with repeated header rows and non-breaking body rows

1.2k Views Asked by At

I’ve been Googling back and forth for a few hours now, out of sheer frustration that the seemingly simple, basic task of printing a table so that page breaks fall in a sane manner appears to be completely impossible to achieve.

 

Background: the HTML/CSS file

The basis for all this is a dynamic HTML/CSS page that represents an invoice. It consists of a header area, a table listing invoice items and a footer area. The header and footer areas are fairly fixed in size (each about 5 cm in height); the table naturally varies greatly depending on how many items the invoice has.

When the invoice is fairly short, printing (or printing to PDF) works fine in most browsers – the entire invoice is on one page, and things look dandy. With longer invoices, though, things completely fall apart and the table gets mangled in various different ways (see “The problem” below).

The body element on the page is a grid container controlling the layout, with the table inside a full-width section. The fiddle at the end of the question shows the structure and the broken layout when printing.

 

The problem: sanely break multi-page tables

What I would like to happen when printing multi-page invoices is, I think, quite reasonable and expected behaviour:

  • The table starts right after the header area as usual
  • If page breaks are necessary, they are added between table rows
  • Table column headers are repeated at the top of each page

But this seems to be impossible in any browser currently available to me for testing.

Firefox, for some reason, refuses to break the table at all and just leaves the entire page empty after the 5-cm header area and puts the table on the following page; if the table itself is too tall for one page, it will add page breaks later on, but ignoring the break-inside setting and chopping letters in half across the page boundary. (It doesn’t seem to want to repeat table headers either, even though most of what I find on Google indicates that this should work in Firefox.)

Chromium browsers like Chrome, Opera and Brave do support both Paged Media and repeated header rows – but they too ignore break-inside for table rows/cells (bug claims to be fixed, but it’s clearly not) and also prints repeated header rows on top of body rows, making the output completely illegible.

Safari seems to be the only browser to actually respect the break-inside setting for table content and only add page break between rows – but it doesn’t support CSS Paged Media at all (for reasons only known to Apple developers), so you can’t control page size, margins, etc., and there doesn’t seem to be a way to get header rows to repeat either.

So, am I missing something essential here, or is it really the case that in 2021, no major browser is seemingly capable of printing tables the way you’d assume they ought to print by default?

Is there a way to print multi-page tables in a way that page breaks are only added between rows, not inside them, while still making sure that header rows are repeated on each page and not printed overlaid on top of body rows?

 

 

Working snippet

@charset "UTF-8";

@page {
  size: A4 portrait;
  margin: 15mm
}

body {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  row-gap: 5mm;
  align-content: start;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 10pt;
  margin: 0;
  padding: 0
}

p {
  font-size: inherit
}

section {
  grid-column: 1/span all;
  height: -webkit-max-content;
  height: max-content;
  font-size: inherit
}

table {
  width: 100%;
  border-collapse: collapse;
  break-inside: auto
}

table td,
table th {
  padding: 1.5mm;
  border-bottom: 1px solid #ccc;
  border-top: 1px solid #ccc;
  vertical-align: top;
  font-size: inherit
}

table thead {
  background: #faf9fc;
  font-weight: 700;
  display: table-header-group
}

table th {
  text-align: left;
  vertical-align: bottom
}

table td,
table th,
table tr {
  break-inside: avoid;
  break-after: auto
}

.address {
  grid-column: span 1;
  display: flex;
  flex-direction: column
}

.address p {
  margin: .5em 0
}

.address p.addressheader {
  margin-bottom: 0;
  font-style: italic
}

.address span {
  margin-right: 1rem
}

.amt {
  text-align: right
}

.date {
  align-self: start;
  grid-column: 2/span 1
}

.invoice-number {
  grid-column: span 1
}

.items {
  margin: 1cm 0
}

.metadata,
.subtotals {
  grid-column: span 2;
  padding: 1.5mm;
  display: grid;
  grid-template-columns: 1fr 2fr
}

.signature {
  display: -webkit-box;
  display: flex;
  justify-content: center;
  align-items: center
}

.signature div {
  width: 60%;
  border-bottom: 1px solid #000;
  display: -webkit-box;
  display: flex;
  align-items: flex-end
}

.signature div p {
  min-width: 30%;
  margin-bottom: 0;
}

.subtotals {
  grid-template-columns: 1fr 1fr;
  text-align: right
}

.subtotals > b {
  text-align: left
}

.title {
  font-size: 24pt;
  font-weight: 700;
  text-transform: uppercase;
  text-align: center;
  align-self: center
}
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Sample Invoice</title>
    </head>

    <body>
        <section class="title">Sample invoice</section>

        <section class="address to">
            <p class="addressheader">Ship to:</p>
            <p>
                <b>Recipient Name</b><br />
                123 Street<br />
                12345 City<br />
                United States
            </p>

            <p>Contact Person Name<br />
                +1 (555) 123-4567<br />
                [email protected]
            </p>
        </section>

        <section class="address to">
            <p class="addressheader">Bill to:</p>
            <p>
                <b>Customer Name</b><br />
                123 Street<br />
                12345 City<br />
                United States
            </p>

            <p>Contact Person Name<br />
                +1 (555) 123-4567<br />
                [email protected]
            </p>
        </section>

        <section class="address from">
            <p class="addressheader">From:</p>

            <p>
                <b>Company Name</b><br />
                123 Street<br />
                12345 City<br />
                United States
            </p>

            <p>Contact Person Name<br />
                +1 (555) 123-4567<br />
                [email protected]
            </p>
        </section>

        <section class="date">
            <b>Invoice Date</b><br />
            5 January 2022
        </section>
        
        <section class="invoice-number">
            <b>Invoice Number</b><br />
            999
        </section>

        <section class="items">
            <table>
                <thead>
                    <tr>
                        <th>HS Code</th>
                        <th>EAN</th>
                        <th>Commodity</th>
                        <th>Origin</th>
                        <th class="amt">Qty</th>
                        <th class="amt">Rate</th>
                        <th class="amt">Total</th>
                    </tr>
                </thead>

                <tbody>
                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>

                    <tr>
                        <td>12345678</td>
                        <td>1234567890123</td>
                        <td>A very useful item</td>
                        <td>US</td>
                        <td class="amt">1</td>
                        <td class="amt">5.00</td>
                        <td class="amt">5.00</td>
                    </tr>
                </tbody>
            </table>
        </section>

        <section class="metadata">
            <b>Tracking number</b><span>1234567890</span>
            <b>Transport type</b>DHL Standard
            <b>Reference</b>999
            <b>Shipment weight (kg)</b>100
            <b>Currency</b>USD
            <b>Inco terms</b>DDP
            <b>Export reason</b>Permanent
            <b>Number of colli</b>3
        </section>

        <section class="subtotals">
            <b>Subtotal</b>125.00
            <b>Freight costs</b>20.00
            <b>Insurance</b>5.00
            <b>Other</b>0.00
            <b>VAT</b>10.00
            <b>Total</b>160.00
        </section>

        <section class="signature">
            <div>
                <p>5 Jan 2022</p>
                [signature]
            </div>
        </section>
    </body>
</html>

0

There are 0 best solutions below