CSS for Markdown, printing

This is the tenth part of some posts on using CSS in and with DT/DTTG. Though I wrote it as a single piece, I broke it up in smaller installments to keep the posts (barely) manageable. Please let me know (preferably by a PM) if you find errors of any kind or are missing details. I’ll correct the first ones and try to help with the second ones.

Previous post

Styles for printing

To print a Markdown document, the environment will most probably first convert it to HTML and then print that. If that is the case, you can use CSS to style the printed document. Like for dark and light mode, you’ll use a media query for that like so:

@media only print {
/* all print styles go here */
}

In addition to all the style definitions you can use in CSS for screen devices, print media provides some attributes to fine-tune the output on paper. Beware, though: not all browsers support the same set of attributes. To find out more about (un)supported attributes, checkout Caniuse.com.

Preventing and forcing page breaks

A page break after a first or second level heading looks bad. For example, a page break directly after a first or second level heading looks bad in print, as does a page break in the middle of a table or between an image and its caption. To achieve page breaks directly after first, second, and third level headings, use

h1, h2, h3 {
  break-after: avoid;
}

Similarly, the attribute break-inside prevents page breaks within an HTML element:

table, pre, figure {
  break-inside: avoid;
}

ensures that tables, code blocks (pre) and figure elements are not disrupted by a page break. This is, of course, only respected if the respective HTML element does not require more space than one page. A table that is too large to fit on a single page will always be split so that one part is printed on one and the next part on the following page.

Preventing page breaks is one thing, another is to force them, for example just before every level two heading. That makes sure that all parts of the document beginning with a level two heading begin at the top of a new page:

body > h2:not(:first-child) {
  break-before: page;
}

The selector body > h2:not(:first-child) matches all h2 elements but the first one. That is useful if the first 2nd level heading comes relatively soon after the first h1 element: having a new page start after only a very short piece of text might look weird. If you don’t think so, simply use h2 as the selector here to have a break before every h2 element.

Make links visible

On screen and in a PDF, links are simply clickable text. The user does not need to know the URL associated with them. In a printed document, that is quite different, since there’s nothing to click on. So, it might be a good idea to have the URL printed together with the link text. Which can be achieved with a simple CSS rule:

p a::after {
  content: " (" attr(href) ") ";
  color: #9999ff;
}

This adds the parenthesized URL after the link text in a light blue color. Note that this will only work for links inside paragraphs. That restriction should be ok for most Markdown documents. Here, the function attr() returns the value of the HTML attribute passed as its argument, namely href. That is the URL. The ::after pseudo-selector matches the place just behind the a element, and content specifies the text to be inserted at the place. Which in this case is the link’s URL in parentheses with a leading and a trailing space.

Enough text at the top and bottom

Having only one or two lines of a paragraph appear at the top or bottom of a page does not look very nice. This kind of trailing or leading text is called „widow“ or „orphan“ in typesetter lingo, and you can easily prevent both of them appearing in your printed Markdown documents:

p {
  orphans: 3;
  widows: 3;
}

Here, the numbers represent lines so that a page break can only occur when at least three lines of a paragraph are printed at the top or bottom of a page.

Other things relevant for printed documents

You can specify the page dimensions of your document using a @page rule:

@page {
  size: A4 portrait;
}

sets the size of the box representing a page in your document. It does not say anything about the physical dimensions of the paper you print to. If you have letter paper in your printer, only the A4 part of the sheets will be used for printing. Similarly, if you use size: legal portrait in your CSS but have letter paper installed in your printer, part of your document might not get printed.

Similarly, you can use the @page rule to specify different margins for left and right pages:

@page :left {
  margin-left: 2.5cm;
  margin-right: 2cm;
}
@page :right {
  margin-left: 2cm;
  margin-right: 2.5cm;
}

That way, the inner margin of a double-sided document is always 2.5 cm.

You can also use the @page rule to specify other CSS attributes for left (odd) or right (even), the first or blank pages. As said before, perhaps not all browsers do already support all CSS attributes in this context. For example, as of October 2022, neither Firefox nor Safari supported the size property. And there might be little use for changing fonts or colors depending on the page that is printed.

6 Likes

This is amazing. Huge thanks.
To clarify

Does this mean all the styling in there ONLY applies when printed via the print dialogue, whether printing to PDF or paper?
If so that is a great thing to have at ones disposal.
Make a crazy colourful screen based CSS but have a more straight forward output built in for printing. Especially if you prefer the simplicity of B&W documents and monochrome printers.

Skip the “dialogue” thingy – it doesn’t matter here. When printing, if to paper or to PDF, the rules inside the @media only print section override those in the rest of the stylesheet. And conversely, whatever you put in this section does not influence the screen one bit. Should’ve made that clearer, I guess.

Check out the print-color-adjust attribute for that. But I’d advise againt a “crazy colourful CSS” anyway. At least if you care about our audience and about accessibility.