Paged.js is an open-source library to paginate content in the browser. Based on the W3C specifications, it’s a sort of polyfill for Paged Media and Generated Content for Paged Media CSS modules. The development was launch as an open-source community driven initiative and it’s still experimental. The core team behind paged.js includes Adam Hyde, Julie Blanc, Fred Chasen & Julien Taquet.

Until we have formal accessible documentation for paged.js, here is a list of links for those who would like to start using paged.js:

Code of paged.js and code examples: https://gitlab.pagedmedia.org/

Main repo of development: https://gitlab.pagedmedia.org/tools/pagedjs

If you want to add your 5 cents worth to the discussion or just contact the team, you can jump and discuss with us on our Mattermost channel.

You can also find below the features we are supporting right now. This text is an extract from the Editoria book.

Page rules

The page rules must be set up in the @print media query.

@media print { }

Size

The size of the pages in a book can be defined by either width and height (in inches or millimeters) or a paper size such as A5 or Letter. It must be the same for all the pages in the book and will be inferred only from the root @page .

@ page { size : A5; } # or @ page { size : 140mm 200mm ; }

Margins

The margin command defines the top, bottom, left, and right areas around the page’s content.

@ page { margin : 1in 2in . 5in 2in ; }

Names

Single pages or groups can be named, for instance as “cover” or “backmatter.” Named pages can have their own, more specific, styles and margins, and even different styles from the main rule.

@ page backmatter { margin : 20mm 30mm ; background : yellow; }

In HTML, these page groups are defined by adding the page name to a CSS selector.

section.backmatter { page : backmatter; }

Page selectors

Blank pages

The blank selector styles pages that have no content, e.g., pages automatically added to make sure a new chapter begins on the desired left or right page.

@page :blank { @top-left { content : none; } }

First page and nth page

There are selectors for styling the first page or a specific page, targeted by its number (named n in the specification).

@page :first { background : yellow; } @page :nth( 5 ) { margin : 2 in ; }

Left and right or recto and verso

Typically, pages across a spread (a pair of pages) have symmetrical margins and are centered on the gutter. If, however, the inner margin needs to be larger or smaller, the selector to style left and right pages can make that change.

@ page :left { margin-right : 2in ; } @ page :right { margin-left : 2in ; }

Margin boxes

The margins of a page are divided into sixteen named boxes, each with its own border, padding, and content area. They’re set within the @page query. A box is named based on its position: for example, @top-left , @bottom-right-corner , or @left-middle (see all rules). By default, the size is determined by the page area. Margin boxes are typically used to display running headers, running footers, page numbers, and other content more likely to be found in a book than on a website. The content of the box is governed by CSS properties.

To select these margin boxes and add content to them, use the following example:

@page { @top-center { content: "Moby-Dick"; } }

Generated content

CSS counters

css-counter is a CSS property that lets you count elements within your content. For example, you might want to add a number before each figure caption. To do so, you would reset the counter for the <body> , increment it any time a caption appears in the content, and display that number in a ::before pseudo-element.

body { counter-reset : figureNumber; } figcaption { counter-increment : figureNumber; } figcaption ::before { content : counter (figureNumber) }

Page-based counters

To define page numbers, paged.js uses a CSS counter that gets incremented for each new page.

To insert a page number on a page or retrieve the total number of pages in a document, the W3C proposes a specific counter named page . The counters declaration must be used within a content property in the margin-boxes declaration. The following example declares the page number in the bottom-left box:

@page { @bottom-left { content: counter(page); } }

You can also add a bit of text before the page number:

@page { @bottom-left { content : "page " counter(page); } }

To tally the total number of pages in your document, write this:

@page { @bottom-left { content : counter(pages); } }

Repeated elements on different pages

Named string

Named strings are used to create running headers and footers: they copy text for reuse in margin boxes.

First, the text content of the element is cloned into a named string using string-set with a custom identifier (in the code below we call it “title,” but you can name it whatever makes sense as a variable). In the following example, each time a new <h1> appears in the HTML, the content of the named string gets updated with the text of that <h1> .

h1 { string-set: title content(text) }

Next, the string() function copies the value of a named string to the document, via the content property.

@page { @bottom-left { content : string(title) } }

Running elements

Running elements are another way to create running headers and footers. Here the content, complete with style and structure, is copied from the text, assigned a custom identifier, and placed inside a margin box. This is useful for formatted text such as a word in italics.

The element’s position is set:

.title { position : running(title); }

Then it is placed into a margin box with the element() value via the content property:

@page { @top-center { content: element(title) } }

Controlling text fragmentation with page breaks

Sometimes there is a need to define how content gets divided into pages based on markup. To do so, paged media specifications include break-before , beak-inside , and break-after properties.

break-before adds a page break before the element; break-after adds a page break after the element.

Here is the list of options:

break-before: page pushes the element (and the following content) to the next available page

break-before: right pushes the element to the next right page

break-before: left pushes the element to the next left page

break-before: recto pushes the element to the next recto page

break-before: verso pushes the element to the next verso page

break-before: avoid ensures that no page break appears between two specified elements

For example, this sequence will create a page break before each h1 element:

h1 { break-before : page; }

This code, in contrast, will push the h1 to the next right page, creating a blank page if needed:

h1 { break-before : right; }

This snippet will keep any HTML element that comes after an h1 on the same page as the h1 , moving them both to the next page if necessary.

h1 { break-after : avoid; }

The last option is the break-inside property, which ensures that the element won’t be separated across multiple pages. If you want to be sure that your block quotes will never be divided, write this:

blockquote { break-inside : avoid; }

Cross-references

To build items such as an index or a table of contents, the export function has to find the pages on which the relevant elements appear inside the book. To do so, paged media specifications include a target-counter property.

For cross-references, links are used that target anchors in the book:

<p>see the <a href="#anchor-name">Title of the chapter</a></p>

Later in the book, the chapter title will appear with the anchor, set using an ID property.

<h1 id="anchor-name">title of the chapter</h1>

The target-counter property is used in ::before and ::after pseudo-elements and set into the content property. As a page counter, it can include some text:

a ::after { content : ", page " target-counter (attr(href), page ); }

In the PDF, this code will be rendered as “see title of the chapter, page 12”.