How do I avoid a page break immediately after a heading
Asked Answered
J

5

55

I have an HTML 4.01/CSS 2.1 document that includes an H3 heading followed by a short (one line) paragraph block and then an unordered list with several items:

<h3>Heading!</h3>

<p>Some things:</p>

<ul>
  <li>Thing one</li>
  <li>Thing B</li>
  <li>Thing 4</li>
</ul>

My problem is that when I print the document (or render it as a PDF using wkhtmltopdf), sometimes a page break will occur right after the heading, before the paragraph, which looks quite silly.

Is there a way to stipulate that page breaks should be avoided immediately after a header? (I'm not averse to HTML5/CSS3 solutions, if that simplifies things significantly.)

Note: following suggestions, I tried using the CSS property page-break-after: avoid. This doesn't really work in any WebKit or Mozilla based browsers, though.

Jehius answered 11/2, 2012 at 8:28 Comment(6)
w3schools.com/cssref/pr_print_pageba.aspWenz
@Wenz - hasn't worked on any browser or converter I'm using. I thought it was one of those widely unregarded CSS things.Jehius
Have you tried applying page-break-after: avoid to the children of the elements it's applying to? So it's not just on the parent (for instance, on the ul plus the lis?Genealogy
Also, and you probably know this, but if you want to really control the printing of information on your site, you probably need to consider PDFs for printing. For instance, TCPDF has the capability to do this somewhat reliably.Genealogy
@JaredFarrish - yeah, I know :/ But the document I have was already in nicely written, print-media-styled HTML, and I thought it would be a small thing to tweak. Unfortunately there's some styling that'll be very hard to reproduce without HTML+CSS.Jehius
Microsoft Edge is the most advanced browser in implementing the print specifications of CSS (like page breaks, orphans and custom @page margins). So my proposed solution is to use Microsoft Edge to print your document. (To be fair, @page margins are supported by WebKit browsers as well).Kerr
M
22

Since the CSS property page-break-after: avoid doesn't work in any WebKit or Mozilla based browsers, use the page-break-inside: avoid over the heading and an acceptable amount of the text:

CSS

<style type="text/css">
    .nobreak {
        page-break-inside: avoid;
    }
</style>

HTML

<div class="nobreak">
    <h3>Heading!</h3>

    <p>Some things:</p>

</div>

    <ul>
      <li>Thing one</li>
      <li>Thing B</li>
      <li>Thing 4</li>
    </ul>
Metheglin answered 11/2, 2012 at 8:34 Comment(13)
Doesn't seem to work in wkhtmltopdf (the statically compiled version from the website) or Firefox 10.0 under Ubuntu 11.10. page-break-inside: avoid does work, for a div wrapped around the whole section though.Jehius
That sucks about wkhtmltopdf. Luckily, its a known issue. You should go upvote it.Metheglin
Just did :) Doesn't work in Mozilla-based browsers either though.Jehius
Re. clear: both — it does now, but no difference. I can hack around it using page-break-inside: avoid on parts of the div, but I'll wait to see if there's a better answer.Jehius
I wouldn't necessarily link to w3schools on SO; many consider it a weak source. See w3fools.com, and MDN for a more reliable resource.Genealogy
My point to asking that was that the clear:both might be causing the page break in wkhtmltopdf. I guess I could have just said that.Metheglin
The trouble with avoiding breaking over the whole block is that it then can't break in the middle of the list, which would be fine (and, indeed, better in some cases). Maybe I just need to re-write the whole thing in LibreOffice or LyX or something; possibly HTML+CSS is just the wrong tool for the job.Jehius
Cant you just wrap the h3 and the first <p> in a class=nobreak div then?Metheglin
Might do that, I think :) Make that your answer and I'll accept.Jehius
it doesn't seem to work for column breaks (nor is it technically supposed to)Bleary
@JaredFarrish: Well in this case it is better to link to w3schools, since it actually has a note on the page-brake-after: Note: None of the browsers support "avoid". There is no such information on the misssing support for avoid on MDN.Personality
it is bad that an extra div is needed to make this work in Chrome.Spotless
Hmmm, Question... I can't use page-break-inside because my parent div contains many children so if I set page-break-inside as avoid, there is a chance that the previous page might have too much left over space as the whole thing will be wrapped to the next page.... any advice.Brainchild
R
104

This is an extremely hacky solution, but it works for me:

h1 {
    page-break-inside: avoid;
}
h1::after {
    content: "";
    display: block;
    height: 100px;
    margin-bottom: -100px;
}

Basically I create an invisible element that increases the size of the <h1> without affecting the content after it. When page-break-inside: avoid is applied and the whole <h1> (including the hacky element cannot fit into the page) the browser is forced to carry the <h1> to the next page.

Reglet answered 12/12, 2018 at 12:17 Comment(13)
+1 I find this actually a good solution working around the lack of support for page-break-after: avoid in browsers. It even allows for precisely defining how large that next block needs to be (and glues another block when necessary). This is much better than the accepted answer because it does not alter the markup by adding some elements without meaning in the content.Atmospheric
This is the only solution that worked for me. I tried everything else suggested in this related SO-question.Portraitist
Dude! that's a superb hackJaquesdalcroze
This worked well for me, but I needed to add a non-whitespace character to the content field. I then set the color to transparent.Outlying
Genius! The only solution that really worked for me. Thanks!Svetlana
I do very rarely post comments on Stackoverflow. But in this case I have to put my infinite gratitude on display. Thanks for the great hack!Fifteen
Great hack. But I had to remove border and padding from the h1 tag for this to work.Reactor
Had to use h3 { margin-top: 1em;margin-bottom: 0.5em; padding-bottom: 0.5em; border-bottom: black solid 2px; page-break-inside: avoid; } h3::after{ content: ""; display: block; height: 5em; margin-top: -0.5em; margin-bottom: -5em; page-break-inside: avoid;Lightish
Amazing! The only decision that worked for this pdf converter: github-wikito-converter. Thanks a lot!Congeneric
This is ingenious!Underarm
This is rediculous that in 2021 I have to use these tricks to avoid page-break after heading and next block.... Solution is great - thanks a lot! But how the heck this page-break spicification and browsers work? I thought that kind of things should work correctly by now ((Cue
Even in 2022 this is apparently sometimes a needed hack. I was unable to get any combo of "break-after:avoid" to work so I ended up using this genius hack. Why do I still need to do this???Sexlimited
Works with wkhtmltopdf and DinkToPdf after adding "page-break-inside: avoid; break-inside: avoid;" inside ::after. It works better with ::before imho.Chaotic
M
22

Since the CSS property page-break-after: avoid doesn't work in any WebKit or Mozilla based browsers, use the page-break-inside: avoid over the heading and an acceptable amount of the text:

CSS

<style type="text/css">
    .nobreak {
        page-break-inside: avoid;
    }
</style>

HTML

<div class="nobreak">
    <h3>Heading!</h3>

    <p>Some things:</p>

</div>

    <ul>
      <li>Thing one</li>
      <li>Thing B</li>
      <li>Thing 4</li>
    </ul>
Metheglin answered 11/2, 2012 at 8:34 Comment(13)
Doesn't seem to work in wkhtmltopdf (the statically compiled version from the website) or Firefox 10.0 under Ubuntu 11.10. page-break-inside: avoid does work, for a div wrapped around the whole section though.Jehius
That sucks about wkhtmltopdf. Luckily, its a known issue. You should go upvote it.Metheglin
Just did :) Doesn't work in Mozilla-based browsers either though.Jehius
Re. clear: both — it does now, but no difference. I can hack around it using page-break-inside: avoid on parts of the div, but I'll wait to see if there's a better answer.Jehius
I wouldn't necessarily link to w3schools on SO; many consider it a weak source. See w3fools.com, and MDN for a more reliable resource.Genealogy
My point to asking that was that the clear:both might be causing the page break in wkhtmltopdf. I guess I could have just said that.Metheglin
The trouble with avoiding breaking over the whole block is that it then can't break in the middle of the list, which would be fine (and, indeed, better in some cases). Maybe I just need to re-write the whole thing in LibreOffice or LyX or something; possibly HTML+CSS is just the wrong tool for the job.Jehius
Cant you just wrap the h3 and the first <p> in a class=nobreak div then?Metheglin
Might do that, I think :) Make that your answer and I'll accept.Jehius
it doesn't seem to work for column breaks (nor is it technically supposed to)Bleary
@JaredFarrish: Well in this case it is better to link to w3schools, since it actually has a note on the page-brake-after: Note: None of the browsers support "avoid". There is no such information on the misssing support for avoid on MDN.Personality
it is bad that an extra div is needed to make this work in Chrome.Spotless
Hmmm, Question... I can't use page-break-inside because my parent div contains many children so if I set page-break-inside as avoid, there is a chance that the previous page might have too much left over space as the whole thing will be wrapped to the next page.... any advice.Brainchild
K
7

If you used HTML 5 <article> and <header>, here's a hack that seems to work with Webkit, Blink and Gecko (tweak the value 8rem to match your needs):

article > header::before
{
    content: "";
    display: block;
    height: 8rem; /* pretend that the header is at least 8rem high so this header cannot fit near the end of page */
    margin-bottom: -8rem; /* however, reduce the margin the same amount so that the following content can stay in its usual place */
    page-break-inside: avoid;
    break-inside: avoid;
}

This works because pseudoelement ::before is rendered downwards from top of the header and browsers do support page-break-inside: avoid; well enough to actually reserve the space at the end of the page. It also uses the fact that browsers consider the height without margins when the space required is actually measured. I don't know if this is specified in any spec or just happens to match the real world browser behavior.

Some of the other answers suggest using ::after instead but in my experience that may result in cases where the container element <article> starts to render on the previous page. Using ::before seems to work better and the start of container element also seems to move. Obviously, the difference doesn't matter if your containing element doesn't have visible edges.

Note that because you have exactly one pseudo-element ::before you might not be able to use this hack if you want to apply some other styles for ::before. This hack requires that the ::before is rendered under the other content but transparent so it cannot contain visible content.

Additional things to consider:

  • The page-break nor page-break-inside do not work inside tables (display:table), display:grid nor display:flex. It's still unknown if this is caused by partial browser implementation or due CSS specification actually requiring this. In practice you need to use display:block for all the parent elements up to <html> or page breaks will happen anywhere.
  • You cannot limit the reserved space to height of full container element. For example, if the whole <article> in the above example is less than 8rem high, the element will still skip to next page because this hack blindly reserves space for 8rem before even trying to fit the <article> on the page.

However, in practice this works better than break-after:avoid or page-break-after:avoid due real world browser support. Also, the support for widows and orphans is really poor, so you may want to apply similar hack for <p> element, too. I would suggest 2rem or 3rem space for those.

Kinghorn answered 6/7, 2021 at 10:25 Comment(0)
B
0

When dealing only with lines inside a Paragraph, you could use the widows and orphans attributes in CSS. But unfortunately this will not bind your header to the Paragraph or the List. This because widows and orphans are not applied on block-like elements (like headers). See Should CSS "orphan" operate at line or block level?

I tried it since I've got stuck with the same problem. It seems to work when I print the Page from the Browser (Opera in my case) but not when I export the PDF with wkhtmltopdf.

Looks like putting all the elements that we don't want to be separated into a div and style this one with page-break-inside: avoid, like suggested in the accepted answer.

In my case where I have to display some headers and tabular data, I had to build a routine that finds the headers and then counts a certain amount of table-rows ahead and relocates the header and the table(s) into one div.

Bandurria answered 7/5, 2020 at 14:11 Comment(0)
I
0

I recently worked on the pdf download story which was having dynamic rows of data in table format which include various charts images(tech used=>Angular + Spring + Thymleaf + Puppeteer) Some of the key points for handling page-breaks

Try to use <div></div>blocks instead of HTML tables

Do not use display: flex on the parent container on which you want page-break-inside: avoid(use float in child element)

.child1{ float: left; }

3.If you are rendering div in loop and page-break-inside: avoid; not working You should use this CSS hack to work on a particular div

<div class="parent-container">
<div class="child1"></div>
<div class="child2"></div>
</div>
.parent-container{
 position: relative;
 page-break-inside: avoid;
} 
.parent-container::after {
content: "";
display: block;
height: 200px;
margin-bottom: -200px;
}
Introgression answered 4/7, 2021 at 16:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.