311

I have a project which requires printing an HTML table with many rows.

My problem is the way the table is printed over multiple page. It will sometimes cut a row in half, making it unreadable because one half is on the bleeding edge of a page and the remainder is printed on the top of the next page.

The only plausible solution I can think of is using stacked DIVs instead of a table and force page-breaks if needed.. but before going through the whole change I thought I could ask here before.

Jeremy W
  • 1,889
  • 6
  • 29
  • 37
h3.
  • 10,688
  • 15
  • 51
  • 54
  • 36
    On a tangent, it might be worth adding a `` to your table with the following css `thead {display: table-header-group; }` so as to print the table-header on all subsequent pages (useful for loooooong data tables). – David Thomas Nov 19 '09 at 14:38

13 Answers13

325
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test</title>
<style type="text/css">
    table { page-break-inside:auto }
    tr    { page-break-inside:avoid; page-break-after:auto }
    thead { display:table-header-group }
    tfoot { display:table-footer-group }
</style>
</head>
<body>
    <table>
        <thead>
            <tr><th>heading</th></tr>
        </thead>
        <tfoot>
            <tr><td>notes</td></tr>
        </tfoot>
        <tbody>
        <tr>
            <td>x</td>
        </tr>
        <tr>
            <td>x</td>
        </tr>
        <!-- 500 more rows -->
        <tr>
            <td>x</td>
        </tr>
    </tbody>
    </table>
</body>
</html>
Wassim Sboui
  • 1,692
  • 7
  • 30
  • 48
Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
  • 1
    This does not work in all browsers. It does seem to work in FireFox 3.6 Mac OS. – daustin777 Oct 08 '10 at 14:23
  • 25
    This also fails in WebKit browsers (eg. Safari and Chrome) – Michael Haren Mar 14 '12 at 19:51
  • only 'thead { display:table-header-group }' css entry works in the latest FF12 and none of them works in Chrome 18+... – mikhail-t Apr 30 '12 at 21:08
  • 5
    While this is the standards-compliant way to do this, the only browser that currently implements the standard is Opera. Note that this is part of css2, and so the lack of implementation is likely to be a problem for some time to come, because apparently no-one cares. – pkh May 08 '12 at 19:16
  • 20
    The CSS 2.1 specification indicates that page break style attributes are only applied to block-level elements. The default display mode for table rows is table-row. Unfortunately, no table elements are block level elements by default, including the table itself. – lsuarez Nov 30 '12 at 21:07
  • @lthibodeaux I see ... "_User Agents must apply these properties to block-level elements in the normal flow of the root element. User agents **may** also apply these properties to other elements, e.g., 'table-row' elements._" (emphasis mine). – Sinan Ünür Dec 02 '12 at 11:29
  • 2
    @SinanÜnür It's not a requirement, so you can't rely on it, and unfortunately from my testing I can see that webkit saw "may" and ignored anything beyond it. Strangely, IE's got some rather nice large table printing support. Never thought I'd sing its praises on any given point. – lsuarez Dec 02 '12 at 16:39
  • @lthibodeaux I had assumed the "I see" in the beginning of my message and the bold **_may_** made it clear that I realize I can't rely on it. The problem with Safari seems to be not in the page breaking, but rather in the fact that `display:table-header-group` and `display:table-footer-group` don't seem to have the effect I consider to be the reasonable and intuitively one. – Sinan Ünür Dec 02 '12 at 23:39
  • 1
    I can confirm it works FINE in the latest Chrome and Safari versions. – Aron Lorincz Oct 09 '15 at 09:09
  • 6
    I can confirm that this does NOT work fine in Chrome or any other Webkit browser (e.g. Safari, Opera)-- unless, by "works fine" you mean "excludes any features that are considered optional". I think what most people want is running headers and footers, and tables that only allow page breaks between rows, neither of which is implemented in Webkit as of 2015/11/13. – DoctorDestructo Nov 13 '15 at 13:12
  • @DoctorDestructo I think a bug report is in order then. – Sinan Ünür Nov 20 '15 at 21:15
  • @SinanÜnür : I just tried your solution, footer is not working as expected : http://stackoverflow.com/questions/38027411/footer-is-not-working-in-page-break-as-expected-in-pdf-generating – Drone Jun 25 '16 at 11:32
  • @MiskateErorr Your statement is false. The HTML shown in your question works fine. You have some other problem. Come up with a minimal case still exhibiting the problem, then update your question. – Sinan Ünür Jun 26 '16 at 11:43
  • 1
    Can confirm that this will make the printing of large tables in chrome 51 a lot more stable. – Thomas Kekeisen Jul 27 '16 at 12:36
  • It’s impressive – and not least _frustrating_ – that a full 12 years later, this **still** doesn’t work properly in any browser. Not Firefox, not Chrome, not Safari, not Opera, not Edge (haven’t tested IE). All of them ignore paged media entirely, ignore the `(page-)break-*` rules or render header and footer rows buggily. Who in the world ever thought it was a good idea to write the CSS 2.1 standard so that these properties don’t have to apply to table rows, quite possibly the place where they’re _most_ crucial? \*sigh\* – Janus Bahs Jacquet Jan 07 '22 at 19:28
  • @JanusBahsJacquet I just tried it in Firefox, Edge, and Chrome on Windows 10 and all showed the expected print priview and what I expected when printing to a PDF file. That indicates to me the problems you are observing might be caused by the styling of elements in the table cells. – Sinan Ünür Jan 11 '22 at 12:42
  • @Sinan I [asked a question](https://stackoverflow.com/q/70598113) about this a few days ago and just added a snippet that represents the structure I have (also available [as a JSFiddle](https://jsfiddle.net/7vgxdrqy/1)). When I print-preview the result from that, I get a mangled layout in all browsers: the table doesn’t break at all (Firefox), header rows don’t repeat (Safari, Firefox) or overlay body rows (Chromium), table rows are broken mid-line (Chromium, Firefox). Do you not see the same? (I’m on a Mac, but that shouldn’t make a difference.) – Janus Bahs Jacquet Jan 11 '22 at 13:49
  • Or rather… Firefox, which has a ‘Print frame’ function that prints just a specific (i)frame, shows the same print preview as on my standalone page. I cannot find a way to print just the embedded result frame from a JSFiddle in Chromium browsers which don’t have an equivalent function; the Chrome extension entitled _Print frame_ doesn’t seem to work. But I have no reason to expect that the JSFiddle would print any differently from the (identical) standalone version. The table cells contain only plain text, nothing else. – Janus Bahs Jacquet Jan 11 '22 at 14:00
  • @SinanÜnür That looks like most of the CSS isn’t being applied, though – no grid for one thing, and no right alignment in the table cells. Did you do/change something else as well? (I reference using a `link ref` as well in the actual file.) – Janus Bahs Jacquet Jan 13 '22 at 01:09
  • 1
    @JanusBahsJacquet You're right, I had made a single character typo. The problem manifests differently in Chrome and Firefox. I don't have time right now, but I am curious. – Sinan Ünür Jan 13 '22 at 02:32
  • @JanusBahsJacquet The way Firefox dealt with the table when I applied your styles is key: It put the table on page two and truncated it. It also labeled the `table` with `overflow`. So, somehow, your `section` + `body` styles are combining to make the table one indivisible block on the grid. Those styles seem to be interfering with the print rules. One can either dive in to CSS grid specs or get into some serious trial & error to figure out how to get the desired display without turning the table into one indivisible block. – Sinan Ünür Jan 13 '22 at 13:43
67

Note: when using the page-break-after:always for the tag it will create a page break after the last bit of the table, creating an entirely blank page at the end every time! To fix this just change it to page-break-after:auto. It will break correctly and not create an extra blank page.

<html>
<head>
<style>
@media print
{
  table { page-break-after:auto }
  tr    { page-break-inside:avoid; page-break-after:auto }
  td    { page-break-inside:avoid; page-break-after:auto }
  thead { display:table-header-group }
  tfoot { display:table-footer-group }
}
</style>
</head>

<body>
....
</body>
</html>
Josh P
  • 1,325
  • 14
  • 12
33

Expanding from Sinan Ünür solution:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test</title>
<style type="text/css">
    table { page-break-inside:auto }
    div   { page-break-inside:avoid; } /* This is the key */
    thead { display:table-header-group }
    tfoot { display:table-footer-group }
</style>
</head>
<body>
    <table>
        <thead>
            <tr><th>heading</th></tr>
        </thead>
        <tfoot>
            <tr><td>notes</td></tr>
        </tfoot>
        <tr>
            <td><div>Long<br />cell<br />should'nt<br />be<br />cut</div></td>
        </tr>
        <tr>
            <td><div>Long<br />cell<br />should'nt<br />be<br />cut</div></td>
        </tr>
        <!-- 500 more rows -->
        <tr>
            <td>x</td>
        </tr>
    </tbody>
    </table>
</body>
</html>

It seems that page-break-inside:avoid in some browsers is only taken in consideration for block elements, not for cell, table, row neither inline-block.

If you try to display:block the TR tag, and use there page-break-inside:avoid, it works, but messes around with your table layout.

vicenteherrera
  • 1,442
  • 17
  • 20
  • 3
    Here's an easy way to add the divs dynamically with jquery: `$(document).ready(function(){$("table tbody th, table tbody td").wrapInner("
    ");});`
    – Chris Bloom Sep 11 '14 at 05:26
  • 1
    Thanks to sinan ürün, vicenteherrera and Chrisbloom7. I applied the combination of your answers and it now works! – Nurhak Kaya Aug 03 '16 at 08:31
  • You might try setting the @media CSS to `tr { display: block; }` for Print Only, rather than adding all the extraneous `
    ` elements. (Haven't tested but worth looking at)
    – Stephen R Oct 30 '19 at 18:39
  • 1
    I added long table in `div` in html file, Then I added `@media print{ thead { display: table-header-group;} tfoot { display: table-footer-group;} div { page-break-inside:avoid !important;} }` to CSS file in media print. Thanks for @sinan and @vicenteherrera answers – ali Feb 13 '23 at 13:10
11

None of the answers here worked for me in Chrome. AAverin on GitHub has created some useful Javascript for this purpose and this worked for me:

Just add the js to your code and add the class 'splitForPrint' to your table and it will neatly split the table into multiple pages and add the table header to each page.

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
MDave
  • 1,245
  • 13
  • 29
  • Do you have sample on how to apply this? I've been trying to assign my table className as `splitForPrint` but in the JS there's nowhere it took the reference of the element using the className `splitForPrint`. Only the part where `var splitClassName = 'splitForPrint';` but that's it. – Compaq LE2202x Aug 20 '14 at 03:05
  • Down voted because the script you linked to does not solve the OP's problem without considerable cherry-picking and reconfiguring, and you didn't provide any examples of how one might go about doing it. – Chris Bloom Sep 12 '14 at 15:05
  • Worked like a charm for me, none of the other solutions worked. +1 Had to add a little css to get the correct breaks .page-break { page-break-after: always; } – fhugas Feb 01 '16 at 17:33
  • Yep by adding .page-break { page-break-after: always; } it saved my day! – milodky May 09 '16 at 21:35
6

Use these CSS properties:

page-break-after

page-break-before 

For instance:

<html>
<head>
<style>
@media print
{
table {page-break-after:always}
}
</style>
</head>

<body>
....
</body>
</html>

via

Chris
  • 3,438
  • 5
  • 25
  • 27
marcgg
  • 65,020
  • 52
  • 178
  • 231
  • I'm not sure, you'll have to check. If not, split into different arrays and separate the arrays by an empty div – marcgg Nov 19 '09 at 14:27
  • You will have to apply it to the table row or even cell, but not to the table, I think. Other than that, it should work. – Pekka Nov 19 '09 at 14:29
  • 2
    does not work in chrome. Is ignored as if 6/13/2012 when applied to TR – ladieu Jun 13 '12 at 19:33
6

I recently solved this problem with a good solution.

CSS:

.avoidBreak { 
    border: 2px solid;
    page-break-inside:avoid;
}

JS:

function Print(){
    $(".tableToPrint td, .tableToPrint th").each(function(){ $(this).css("width",  $(this).width() + "px")  });
    $(".tableToPrint tr").wrap("<div class='avoidBreak'></div>");
    window.print();
}

Works like a charm!

Mr. Pyramid
  • 3,855
  • 5
  • 32
  • 56
3

I ended up following @vicenteherrera's approach, with some tweaks (that are possibly bootstrap 3 specific).

Basically; we can't break trs, or tds because they're not block-level elements. So we embed divs into each, and apply our page-break-* rules against the div. Secondly; we add some padding to the top of each of these divs, to compensate for any styling artifacts.

<style>
    @media print {
        /* avoid cutting tr's in half */
        th div, td div {
            margin-top:-8px;
            padding-top:8px;
            page-break-inside:avoid;
        }
    }
</style>
<script>
    $(document).ready(function(){
        // Wrap each tr and td's content within a div
        // (todo: add logic so we only do this when printing)
        $("table tbody th, table tbody td").wrapInner("<div></div>");
    })
</script>

The margin and padding adjustments were necessary to offset some kind of jitter that was being introduced (by my guess - from bootstrap). I'm not sure that I'm presenting any new solution from the other answers to this question, but I figure maybe this will help someone.

Aaron
  • 2,409
  • 29
  • 18
1

I faced the same problem and search everywhere for a solution, at last, I fount something which works for me for every browsers.

html {
height: 0;
}

use this css or Instead of css you can have this javascript

$("html").height(0);

Hope this will work for you as well.

Jay
  • 19
  • 2
0

I checked many solutions and anyone wasn't working good.

So I tried a small trick and it works:

tfoot with style:position: fixed; bottom: 0px; is placed at the bottom of last page, but if footer is too high it is overlapped by content of table.

tfoot with only: display: table-footer-group; isn't overlapped, but is not on the bottom of last page...

Let's put two tfoot:

TFOOT.placer {
  display: table-footer-group;
  height: 130px;
}

TFOOT.contenter {
  display: table-footer-group;
  position: fixed;
  bottom: 0px; 
  height: 130px;
}
<TFOOT  class='placer'> 
  <TR>
    <TD>
      <!--  empty here
-->
    </TD>
  </TR>
</TFOOT> 
<TFOOT  class='contenter'> 
  <TR>
    <TD>
      your long text or high image here
    </TD>
  </TR>
</TFOOT>

One reserves place on non-last pages, second puts in your accual footer.

Minal Chauhan
  • 6,025
  • 8
  • 21
  • 41
0

I have a face like this problem. You can solve this problem using CSS properties.

    @media print {
       table{page-break-after: auto;}
   }

Note: You can not use this property with empty or on absolutely positioned elements.

-2

I've tried all suggestions given above and found simple and working cross browser solution for this issue. There is no styles or page break needed for this solution. For the solution, the format of the table should be like:

<table>
    <thead>  <!-- there should be <thead> tag-->
        <td>Heading</td> <!--//inside <thead> should be <td> it should not be <th>-->
    </thead>
    <tbody><!---<tbody>also must-->
        <tr>
            <td>data</td>
        </tr>
        <!--100 more rows-->
    </tbody>
</table>

Above format tested and working in cross browsers

Neil
  • 14,063
  • 3
  • 30
  • 51
-3

The accepted answer did not work for me in all browsers, but following css did work for me:

tr    
{ 
  display: table-row-group;
  page-break-inside:avoid; 
  page-break-after:auto;
}

The html structure was:

<table>
  <thead>
    <tr></tr>
  </thead>
  <tbody>
    <tr></tr>
    <tr></tr>
    ...
  </tbody>
</table>

In my case, there were some additional issues with the thead tr, but this resolved the original issue of keeping the table rows from breaking.

Because of the header issues, I ultimately ended up with:

#theTable td *
{
  page-break-inside:avoid;
}

This didn't prevent rows from breaking; just each cell's content.

-3

Well Guys... Most of the Solutions up here didn't worked for. So this is how things worked for me..

HTML

<table>
  <thead>
   <tr>
     <th style="border:none;height:26px;"></th>
     <th style="border:none;height:26px;"></th>
     .
     .
   </tr>
   <tr>
     <th style="border:1px solid black">ABC</th>
     <th style="border:1px solid black">ABC</th>
     .
     .
   <tr>
  </thead>
<tbody>

    //YOUR CODE

</tbody>
</table>

The first set of head is used as a dummy one so that there won't be a missing top border in 2nd head(i.e. original head) while page break.

Aneesh
  • 1
  • 1