As you progress in web development, it becomes easier to think of styles as modes, a way of viewing a site in different contexts and on different devices. The content of your site is HTML: static, unchanging data. CSS acts as a filter, emphasizing, reducing, eliminating or moving content in a way appropriate to each client.

You can embed your print styles directly in HTML, but this will cause an extra HTTP request, which may slow down the page loading:

<link media="print" href="printable.css" />

The best way to embed print styles is to declare the @media query, that was introduced since CSS3 released:

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

Between the opening and closing curly braces you will be writing the style rules for your pages when they are printed. There is no need to duplicate your earlier rules, only to indicate what is different when a page is printed. However, you are not limited to writing stylesheets, you could make the printed page appear completely different from the web version.

Hide elements that are irrelevant to the printed page

When someone prints your site, they are interested in content, and everything else, like navigation bars, headers, and footers aren't relevant to the content. Therefore, in the print @media query, we turn off their visibility.

header, footer, nav, aside, .non-printable {
    display: none;
}

Hide background images, and change colors to high-contrast

Make sure you are setting background-image: none in your @media print rules for elements that have a background image set. Also we should reset styles like color, box-shadow and text-shadow to be better seen on the paper.

*:not(a),
*:not(a):before,
*:not(a):after,
*:first-letter:not(a),
p:first-line,
div:first-line,
blockquote:first-line,
li:first-line {
    background: transparent !important;
    color: #000 !important;
    box-shadow: none !important;
    text-shadow: none !important;
}
Force background printing

On the other hand, you might be interested to force browser to display background, so we need to overwrite the default settings for some browsers:

header {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
}

Use absolute units (which are not pixels) for measurements

"Pixels" have no relevance to the printed page, and you can usually assume that any printer is using standard paper. Responsive design principles – measuring most elements in percentages or other scalable units – usually works best, but it's also safe and recommended to use absolute units like cm (centimeters), mm (millimeters), in (inches), pt (points) or pc (picas).

header {
    margin-bottom: 1cm;
}

Revealing links and abbreviations

Obviously, the reader cannot "click" on the printed page to see where a link goes. However, it is still useful to indicate URL addresses. To do this, we use some advanced CSS in the @media print section:

a[href^=//]:not([href*="example.com"]):after,
a[href^=http]:not([href*="example.com"]):after {
    content: " <" attr(href) ">";
}

These lines mean, we can filter links to show only external links, as we don't want to show reference of the a tag, that could be used as an anchor.

Abbreviations should be wrapped in abbr elements and their expansions included in the title attribute. It makes sense to display those on printed pages:

abbr[title]:after {
    content: " (" attr(title) ")";
}

Force page breaks

Since printed pages aren’t endless like web pages, content will eventually break on one page and continue on the next page.

Page breaks before an element

You might wish each product presented on the site to be printed on a new page, or a fresh page used for every individual entry on a blog. If you have been consistent in your markup, and use an h2 element exclusively for each new item, you could write the following within your @media query:

h2 {
    page-break-before: always;
}

It ensures that when an h3 element will be encountered, it always starts a new page: for all intents and purposes, h2 elements become page headings when printed. The one downside to this approach is that your first page may be essentially blank, or very short.

When you have a series of article entries presented on the same web page, you might like to start a new page when an article follows another article:

article + article {
    page-break-before: always;
}
Page breaks after an element

We can prevent page breaks right after sub-headings because that looks odd:

h3, h4, h5, h6 {
    page-break-after: avoid;
}
Page breaks inside an element

While browsers will logically split long paragraphs across pages when they are printed, you typically do not want that to occur to images, tables, lists and other elements:

ul, pre, code, blockquote, table, img {
    page-break-inside: avoid;
}
Widows and Orphans

Sometimes you may not want to force a page break, but at least control how many lines are displayed on the current or the next page.

if the last line of a paragraph doesn’t fit on the current page, the last two lines of that paragraph will be printed on the next page, because the property that controls this, widows is set to 2 by default. If it's the other way around and only one line fits on the current page, the whole paragraph will be printed on the next page. The property responsible for this behavior is orphans and its default value is 2 as well. But we can overwrite these values:

p {
    widows: 4;
    orphans: 3;
}

Testing

You don't have to print a page every time you make a small change. Depending on your browser you can export the page as a PDF, show a print preview or even debug directly in the browser.

The most convenient way to check your printable stylesheets is probably Google Chrome built-in functionality in DevTools. It could emulate CSS media to preview the page.

Testing print stylesheets using Chrome DevTools