Print CSS notes

August 3rd 2024

Writing specific print CSS was something I used to do a lot more in the past when there seemed to be a need for it. The demand for printing on paper declined due to sustainability concerns. But I have recently returned to adding print CSS, not for printing itself but for the sake of using the print function to save a webpage as PDF instead.

A better approach for sharing resources

To make sure that my supporting materials for teaching are accessible and inclusive, I review, revise and edit everything during the yearly updates. Over the years, I’ve used WordPress sites to publish the session notes as posts, which were tagged and ordered for easy access. I’ve also provided the same content as PDFs to download for those who would like to have an offline copy or printed version. Most recently, I’ve switched back to static HTML and 2 years ago, I decided on focusing on HTML alone with an added print CSS for better output to print or saving to PDF.

My main motivation for dumping the PDF versions were the accessibility concerns as well as the upkeep and time needed for maintaining 2 versions of the same content. This had proven to be much more laborious as well as far more prone to errors, of course, than maintaining only one version alone.

Sustainability

In addition to accessibility, another core focus now has to be the most effective and sustainable delivery of content. I knew that HTML is often lighter in file size than a PDF but this had slipped to the back of my mind until the following post from Léonie reminded me.

Léonie Watchon: So, it turns out that #HTML is more climate friendly than #PDF, as well as more accessible.


To quote the article on gov.uk:

PDF and climate change
Perhaps the most surprising outcome from the panel was about Carbon Dioxide emission in relation to the use of PDF as a communication medium.
[…]
What is evident across all methods of analysis however is that larger file size = more data transferred = more CO2 emitted. This is particularly evident when PDFs use large amounts of visual content, such as decorative graphics and high-resolution images or diagrams.

Read the full post:
Making a positive change: PDF to HTML

As notes for myself as well as anyone looking into print CSS, this post collects some common practices and links to articles as references. Hopefully this will make it easier to tackle the somewhat tricky nature and unpredictability of the final outputs to print or PDF.

Print CSS

The following list outlines the typical aspects to address with the CSS rules for print:

  1. Consider colours and adjust. (view code example ↓)
    Background colours and images will be removed automatically but the use of colour for text still needs to be checked and re-considered. Colours should be optimised to remain legible on the typically white background. Alternatively, if we want to use a dark colour scheme, we need to consider margin/bleed settings as well as checking that the colour contrast remain legible for screen-viewing.
  2. Remove unnecessary elements. (view code example ↓)
    The website’s menu, for example, is not needed for the context of the page; footer content often includes mainly links which would not be important or helpful for a printed/PDF version.
  3. Add full URLs to links within content. (view code example ↓)
    Links within text would be included without the URL itself, but using the Attribute selector, the URL of any link can be made visible and included right after any link.
  4. Adjust layout and image sizes. (view code example ↓)
    Considering the typical print output to the A4 (aspect ratio: 1:1.41), it will be important to adjust the layout to fit. This might mean single column layouts, and setting all medium/large images to block and full width.
  5. Show poster image for videos (view code example ↓)
    For video embeds and similar content, we can use the poster image as static placeholder and if possible, include a link to the video in addition. This could be the link to where the video is hosted if using an external platform, or a download link for the file itself.
  6. Show toggled content (view code example ↓)
    If the page contains hidden content which is only visible via the use of toggles, for example, this will need to be addressed specifically. The print function will merely include anything that is visible at the time. Most pages with content toggles will print the full page only if all toggles are open at the time of printing or saving to PDF. This means that our print CSS will have to open the toggles before printing ~ an override of the toggle function will have to be put in place.
  7. Control page breaks. (view code example ↓)
    One of these more tricky and difficult aspects for a print/PDF version is the page break. When sending to print, all visible content will be printed, and additional pages added as required. For a better flow of content, we can set rules to either add or avoid a page break.

CSS code for print/PDF version

To implement the print CSS, we create a new, appropriately named file, saved in the same directory as the main styles file and add the link to the <head> of our HTML file, targeting print as media:

<link href="../css/print.css" media="print" rel="stylesheet">

Once in place, we can add the new rules to this file and test via the print function in the browser. As first rule, we’ll use the universal reset.

/* reset universal defaults */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

Another consideration at the start will be the overall font size. By default, the standard size of 12pt is likely to be implemented when the print function is used. This is a standard for most printed text and will not need to be set specificially. However, if need be, we could use this as part of the initial body settings ~ and even use a larger size here if our target group requires it.

/* set font size */
body {
    font-size: 12pt;
}

Most of the time we do not need to worry about the outer margins for print/PDF as this will be inherent when using the print function. We could however set a specific page size and margin as follows:

@page {
    size: A4;
    margin: 2cm;
}

1. consider colours and adjust.

Most commonly, we will remove any background colour and set the text to black. Setting the text colour for the <body> will affect all text elements.

/* remove background image/colour and set text colour */
body {
    background: none;
    color: #000;
}

To keep the colour for the headings as implemented via our main CSS for the online version, we can set this:

/* reset colour for headings */
h1, h2, h3, h4, h5, h6 {
  color: initial; 
}

Of course, we could also change the colour if need be for ensuring sufficiently high contrast.

2. remove unnecessary elements

This is an easy one, we can simply list the elements we want to exclude from the print/PDF version and hide them. It will be important to carefully consider the context and purpose of the page in hand and keep anything that might be of relevance but remove anything that does not have any meaning or useful information to declutter the new version of the page.

/* hide site nav and footer */
nav, footer {
    display: none;
}

3. add full URLs to links within content.

For resources which include links to references and external sites, this is a vital detail which has to be included to ensure that the page version remains as useful as its online version.

/* include URL after the link in square brackets */
a:after {
    content: " [" attr(href) "]";
    font-size: 0.8em;
}

4. adjust layout and image sizes.

This adjustment will be very specific to the page in hand. A fitting approach might be to look at the rules in place for mobile/tablet view and reuse some of those settings to fit. By default, the print option should result in the removal of columns for layout. However, for a cleaner and balanced presentation of content it is likely that we need to review the margins for individual text elements, such as lists or headings, for example. We will merely write rules to tweak the spacing. Margins are then set in print units, typically centimeters.

/* consistent bottom spacing for headings, paragraphs and lists */
h1, h2, h3, h4, h5, h6 {
    margin-bottom: .3cm;
}
p, ul, ol {
    margin-bottom: 1cm;
}

Medium and larger images which might be presented side-by-side with text will be best set to full page width. We’ll set these to block and adjust margins in centimeters.

/* adjust large image sizing */
img {
    display: block;
    width: 100%;
    height: auto;
    margin: .5cm 0;
}

Smaller inset images can be set to specific sizes, again, using the print-specific units. The following is an example of a thumbnail image set to about a third of the overall page with and right-aligned.

/* adjust thumbnail image sizing and positioning */
img.thumbnail {
    float: right;
    width: 6cm;
    height: auto;
    margin: .5cm 0 .5cm .5cm;
}

A note on flex-box and grid based layouts

Sadly, the settings for layout with flex-box or grid do not always get removed for print as expected. It will be a matter of checking on the print preview and adjust settings accordingly if needed. We can override the flex-box or grid settings by applying display: block and width: 100%.

5. Show poster image for videos

For media content such as videos, a placeholder will be the most suitable alternative. For this, the <video> tag in the HTML will need to include the poster image which can then be included instead of the video file. The poster image will be shown while the video is downloading. If not specified, nothing is displayed until the first frame is available, then the first frame is shown as the poster frame.

The following is an example of the video tag, including the poster as well as a message (with a fallback link to download the MP4) should the video fail to load.

<video width="800" height="600" poster="poster.jpg" controls>
  <source src="video.mp4" type="video/mp4">
  Unfortunately, your browser does not support the video tag. 
  You are welcome to <a href="video.mp4">download the video file</a> instead.
</video>

There are different solutions on how to include the poster image for the print/PDF version. The simplest method is to include the image as shown in the HTML snippet and set the video to be hidden.

/* hide video, poster image will be shown instead */
video {
    display: none;
}

Should this approach fail, we could use generated content for the print CSS instead:

/* after hiding the video, add image with width + height set to the same size as the video file */
video::before {
    content: url('poster.jpg');
    display: block;
    width: 800px; 
    height: 600px; 
}

6. Show toggled content

The code required for showing hidden/toggled content will depend on the actual markup and CSS as there are different methods of setting this up. Here’s an example of a simple toggle using checkboxes as a demo of how this might be solved.

<!-- toggle link -->
<input id="article-tog" class="toggle" type="checkbox">
<label for="article-tog" class="lbl-toggle">index</label>
<!-- toggled content -->
<article>
    <!-- content here -->
</article> 

The following is the default CSS for use in the browser to hide the checkbox and use the label as clickable element to show or hide the article:

/* hide checkbox, use opacity to ensure accessibility */
input[type='checkbox'] {
    opacity: 0;
}
input[type="checkbox"]:focus + label::before {
    box-shadow: 0 0 0 0.2em #fff, 0 0 0 0.4em #000;
    outline: none;
}
/* toggle */
.lbl-toggle {
    position: relative;
    display: block;
    transition: all 0.25s ease-out;
}
.lbl-toggle:hover, .lbl-toggle:active, .lbl-toggle:focus {
    color: #333;
    cursor: pointer;
}
/* toggle indicator */
.lbl-toggle::before {
    content: ' +';
    display: block;
    width: .6em;
    height: .6em;
    color: #ccc;
    transform-origin: center;
    transition: transform .2s ease-out;
    transform: rotate(45deg);
    float: left;
    margin: .2em .3em 0 -.6em;
}
/* toggled content: hide/show */
.lbl-toggle + article {
    max-height: 0;
    overflow: hidden;
    transition: max-height .25s ease-in-out;
}
.toggle:checked + .lbl-toggle + article {
    max-height: 9000vh;
    overflow: visible;
}

For the print CSS, we will now set the <article> to be shown for both scenarios, for the checked as well as the unchecked checkbox:

/* always show toggled content */
.lbl-toggle + article, .toggle:checked + .lbl-toggle + article {
    max-height: 9000vh;
    overflow: visible;
}

7. Control page breaks.

This is one aspect which seems fairly unpredictable and difficult to resolve. While setting a page break after a specific element mostly works (at times, an extra element needs to be added to the HTML), preventing elements from being split onto 2 pages seems quite tricky and difficult. I have not found a reliable solution so far.

/* avoid splitting content, i.e. no page break */
table, figure, img {
    break-inside: avoid;
}

Closing thoughts

These points should cover most aspects. While many of these rules are quite easy to implement, the issue of page breaks remains tricky and often difficult to control. It will therefore be vital to test and tweak to ensure the correct presentation of our pages as PDF or in print. The dev tool inspector includes a toggle to preview the print format however this does not include the page break itself. I found that using the print function for preview is the most reliable way to test.

Links & references