Published: October 30, 2024
From Chrome 131 you can use CSS to add content to the margins of pages when they are printed. This post explains the page model for paged media, and how to use this feature to improve print output from your webpages.
CSS includes specifications that deal with paged media, the CSS Paged Media Module and CSS Generated Content for Paged Media. They define features that are only used when a page is printed (including to PDF). There are user-agents that support this CSS, and let you generate books and other printed material from HTML and CSS. However, this functionality has never been well supported in web browsers, despite the fact that we quite often do need to print or create PDFs from applications.
Chrome and Firefox support the
@page
at-rule. This rule
lets you define the size of the page you are printing to, and the size of the
margins around the content. From Chrome 131, you can also use generated content
to add content to the margins, by targeting the relevant margin at-rule.
The page model
The page model used in paged media defines a page box, this is your sheet of paper. Inside the page box is a page margin, a page border, page padding, and finally the page area where your content is displayed. When content is printed it is fragmented into as many pages as needed to contain it.
The page margin is then split into 16 boxes, with each having a corresponding at-rule.
@top-left-corner
@top-left
@top-center
@top-right
@top-right-corner
@left-top
@left-middle
@left-bottom
@right-top
@right-middle
@right-bottom
@bottom-left-corner
@bottom-left
@bottom-center
@bottom-right
@bottom-right-corner
Margin box sizing
The height of the top and bottom boxes and width of the left and right side
boxes is defined by the margin size set using @page
. The corner boxes
therefore have a fixed size created by the intersection of those margins. The
three boxes between each corner however are flexible. They behave in a similar
way to boxes in a flex layout using flex: auto
, so they stretch to fill the
space, but if you put a long string of text in one, and nothing in the others,
the one with the text will grow rather than wrap the text.
Add content to margin boxes
To add content to margin boxes use CSS generated content, just as you would with
the ::before
and ::after
pseudo-elements. In this case, use the at-rule
related to the box you want to target. The following CSS adds the text "My book"
to the bottom left margin box or right-hand pages. It also styles that text.
@page :right {
@bottom-left {
content: "My book";
font-size: 9pt;
color: #333;
}
}
As well as strings of text, you can add page counters to the margins. The
predefined page
counter contains the current page. The following CSS adds it
to the bottom-right of right-hand pages and the bottom-left of left-hand pages.
@page :right {
@bottom-right {
content: counter(page);
}
}
@page :left {
@bottom-left {
content: counter(page);
}
}
There's also a pages
counter that always contains the total number of pages.
Things to note when using page margins
If printing from a browser, the browser will automatically add some page margin content if there is room for it to display. It will do this even if you have added content. These automatically generated headers and footers can be turned off in the print dialog.
If you set the margins on a page to 0, or a value so small that there is no space for the browser headers and footers, they won't display.
Due to the concept of a default page layout in Chromium, if the first page of your printed document has no room for the automatic content, this will prevent the browser content from displaying on later pages, even if they do have space.
Future possibilities for paged media
The paged media specifications include several other features, described in the article Designing for Print with CSS. If you have a use case for any of the following features, add it to the linked bugs.
Set strings
Books often print the current chapter title in the margins. This title can't be
hardcoded into your CSS as it changes as you move through the book. The
string-set
property lets you set a value from your HTML to then use in the
generated content. The following CSS assumes that chapter titles are marked up
as an <h1>
. It takes the content of each <h1>
and uses it in the top-right
margin on right-hand pages. When it gets to the next <h1>
the value is updated
for margins after that point.
h1 {
string-set: doctitle content();
}
@page :right {
@top-right {
content: string(doctitle);
margin: 30pt 0 10pt 0;
font-size: 8pt;
}
}
Chromium bug for string-set
and string()
.
Cross-references
Once a document is printed, references to other pages are usually indicated by
using the page number where the reference can be found. These cross-references
can be created using target-counter
. When applied to a link, the link works to
jump to the reference on the web, when printed the page number is shown.
<a class="xref" href="#ref1">my reference</a>
a.xref:after {
content: " (page " target-counter(attr(href, url), page) ")";
}
Chromium bug for cross-references.
Footnotes
Footnotes are also a feature of the paged media specifications. In HTML, use a class to identify text that should be a footnote, for example:
<p>This is some text <span class=”fn”>this is a footnote</span>.</p>
Then use the footnote
value of float
, to turn this text into footnotes. It
will be removed from the paragraph when the document is printed and shown as a
footnote.
.fn {
float: footnote;
}