CSS @page Rules: Page Size, Margins, Named Pages and Margin Boxes
The CSS @page rule sets up the printed page from the style sheet: its size and orientation, its margins, different pages for the first, left and right pages or for parts of the document, and running headers and footers with page numbers in the page margins. With the v42 layout engine, PD4ML follows @page as CSS Paged Media 3 defines it and as Chrome prints it, so a document styled for printing in a browser comes out the same way without any PD4ML-specific API calls or tags.
@page {
size: A4;
margin: 20mm 15mm;
@top-right { content: "Annual Report 2026"; font-size: 9pt; color: #666 }
@bottom-center { content: "Page " counter(page) " of " counter(pages) }
}
@page :first {
margin-top: 60mm;
@top-right { content: none } /* no running header on the title page */
}
@page wide { size: A4 landscape }
.appendix-table { page: wide } /* this section gets landscape pages */
Available from PD4ML v4.2.0, with the v42 layout engine: call useHtmlRenderer(Constants.LayoutEngine.v42) before readHTML(). The v40 engine reads only size and the page margins from @page.
What is supported
| Feature | Syntax | Notes |
|---|---|---|
| Page size | size: A4, size: 150mm 100mm, size: letter landscape |
keywords, lengths, orientation |
| Page margins | margin, margin-top, margin-right, margin-bottom, margin-left |
lengths, percentages |
| First page | @page :first |
the first page of the document |
| Left and right pages | @page :left, @page :right |
the first page is a right page |
| Blank pages | @page :blank |
pages PD4ML leaves empty on purpose |
| Named pages | @page wide { ... } with page: wide on elements |
page breaks where the name changes |
| Margin boxes | @top-left, @top-center, ..., @bottom-right-corner (16 boxes) |
text and page counters in the margins |
| Page counters | counter(page), counter(pages) |
with counter styles: counter(page, upper-roman) |
Page size
| Value | Page |
|---|---|
size: auto |
the page size set through the API, or A4 (the default) |
size: A5 |
a page size keyword, portrait |
size: A4 landscape, size: landscape A4 |
a keyword in the given orientation |
size: landscape |
the API's (or default) page size, turned to landscape |
size: 210mm 297mm |
width and height |
size: 6in |
one length: a square page |
The page size keywords of CSS are A5, A4, A3, B5, B4, JIS-B5, JIS-B4, letter, legal and ledger (11 x 17 in). PD4ML also knows its own named formats: A0 to A10, ISOB0 to ISOB5, halfletter, tabloid, note and ARCHA to ARCHE1. Keywords are case-insensitive.
Lengths can be given in mm, cm, in, pt, pc, px (1/96 in), Q, em and rem (16 px each). They are physical sizes: size: 150mm 100mm gives a 150 x 100 mm PDF page whatever setHtmlWidth() is.
A size that is not valid - an unknown keyword, a zero or negative length, three values - is ignored, as in a browser.
Page margins
@page { margin: 25mm 20mm 30mm } /* top, left and right, bottom */
@page { margin: 10mm; margin-left: 35mm } /* longhands override the shorthand */
@page { margin: 5% } /* left/right: of the page width; top/bottom: of its height */
The margin shorthand takes one to four values as everywhere in CSS. Percentages of margin-left and margin-right are of the page width, of margin-top and margin-bottom of the page height. Negative margins are taken as 0, and auto leaves the margin as the API (or default) sets it. Margins are physical lengths, like size.
The content of the document is laid out in the box the margins leave: its width is the layout width of the document. A pd4ml:page.header or pd4ml:page.footer takes its room from inside that box, below the top margin and above the bottom margin; @page margin boxes, below, go into the margins themselves.
Page selectors: first, left, right and blank pages, page names
@page { margin: 20mm }
@page :first { margin-top: 70mm } /* room for a title block */
@page :left { margin-left: 25mm; margin-right: 15mm }
@page :right { margin-left: 15mm; margin-right: 25mm } /* binding margin on the inside */
@page cover:first { margin: 0 } /* a name and a pseudo-class together */
@page :left, :right { margin-bottom: 25mm } /* selector lists work */
:firstis the first page of the document.:leftand:right: pages alternate, and the first page is a right page (a left-to-right document starts on the right side of a spread). Page 1, 3, 5, ... are right pages, 2, 4, 6, ... left pages.:blankis a page PD4ML leaves empty on purpose: the filler page<pd4ml:page.break next="even">ornext="odd"adds to make the next page even or odd.- A page name (
@page chapter) matches the pages of elements withpage: chapter(see Named pages below). It can be combined with the pseudo-classes:@page chapter:leftmatches the left pages among them, and@page cover:firstthe first page of the document if it is acoverpage. (:firstis always the first page of the document, not the first page of each run of named pages.)
Several rules can match one page. Their declarations cascade as style rules do: a more specific rule wins - a page name counts more than :first or :blank, which count more than :left or :right - then the later rule; !important wins over the rest. A margin box set in one rule and a property of it in another combine the same way.
The whole document is laid out at one width: that of its first page. Pages that :first, :left or :right give other side margins get those margins - the content is placed between them - but the content keeps the layout width of the document, so it may stick out on the narrower side. Different top and bottom margins are not affected: they only change how much content fits on a page. To have parts of a document laid out at another width, use named pages.
Named pages
The page property gives a part of the document pages of its own kind:
@page { size: A4; margin: 15mm }
@page wide { size: A4 landscape; margin: 10mm }
@page cover { margin: 0 }
.cover { page: cover }
.big-data { page: wide }
<body>
<section class="cover"> ... title page, edge to edge ... </section>
<section> ... report text, A4 portrait ... </section>
<section class="big-data"> ... a wide table, on landscape pages ... </section>
<section> ... more text, A4 portrait again ... </section>
</body>
- A block whose page name differs from that of the content before it starts on a new page, and the content after it, back with another name, does too. Elements without a
pageproperty (page: auto, the default) have the name of their parent. - Those pages take the
@pagerules of their name in addition to the unnamed ones: here thebig-datasection is printed on landscape A4 pages with 10 mm margins, as many pages as it needs. - The content of a named page is laid out at that page's own width, as Chrome does: a table with
width: 100%in the landscape section fills the landscape width, and text lines there are longer. - A name that has no
@pagerule still forces the page breaks, with the unnamed page's size and margins.
A named page gets a layout width of its own when the named element is a child of <body> (a <section>, <div>, <table> directly in the body), the usual way to structure a document. A named element nested deeper - inside a wrapper <div> - still starts its own pages with the named page size and margins, but its content keeps the layout width of the content around it.
Margin boxes
The page margins hold sixteen margin boxes, three along each edge and one in each corner, for running headers, footers and page numbers:
top-left-corner | top-left | top-center | top-right | top-right-corner
-----------------+-------------------------------------------+-----------------
left-top | | right-top
| |
left-middle | page content | right-middle
| |
left-bottom | | right-bottom
-----------------+-------------------------------------------+-----------------
bottom-left- | bottom-left | bottom-center | bottom-right | bottom-right-
corner | | | | corner
A margin box is declared inside an @page rule, and is shown when its content has something to show:
@page {
margin: 25mm 20mm;
@top-left { content: "ACME Corp." ; font-weight: bold }
@top-right { content: "Confidential"; color: #c00 }
@bottom-left { content: "Printed 2026-11-01" }
@bottom-right { content: "Page " counter(page) " of " counter(pages) }
@right-middle { content: "DRAFT"; font-size: 18pt; color: #ccc }
}
@page :first {
@top-left { content: none } /* not on the title page */
@top-right { content: none }
}
Content
content takes a list of:
| Value | Shows |
|---|---|
"text" or 'text' |
the text |
counter(page) |
the page number |
counter(pages) |
the number of pages of the document |
counter(page, upper-roman) |
the number in a counter style: decimal, decimal-leading-zero, lower-roman, upper-roman, lower-alpha/lower-latin, upper-alpha/upper-latin, lower-greek |
none, normal |
no box |
counter(page) is the number PD4ML prints on the page: it starts at setInitialPageNumber() and follows a page counter reset of <pd4ml:page.break pageCounter="...">, the same number as $[page] in a pd4ml:page.footer.
Placement and size
- The corner boxes fill the corners of the page: the width of the left or right margin by the height of the top or bottom margin.
- The top and bottom boxes share the top and bottom margin between the corners. With a centre box, it is centred and gets the room its content needs; the left and right boxes take the rest on either side. Without a centre box, the left and right boxes share the edge by the room their contents need. A box alone takes the whole edge. (CSS Paged Media 3, 5.3.2.)
- The left and right boxes share the side margins between the corners in the same way, top to bottom.
The default alignment is that of the spec: top-left and bottom-left text is left-aligned, *-center centred, top-right and bottom-right right-aligned; the left and right side boxes are centred; a corner's text sits towards the page content. Top and bottom boxes are centred vertically in the margin; left-top/right-top sit at the top, left-bottom/right-bottom at the bottom. text-align and vertical-align (top, middle, bottom) change this.
Styling
A margin box takes the usual text and box properties: font-family, font-size, font-weight, font-style, color, text-transform, letter-spacing, background, border (border-top: 1px solid gives a rule above a footer), padding and margin. Unset text properties are inherited from the root element (<html>), not from <body> - as in a browser, so a font set only on body does not reach the margin boxes:
html { font-family: "Helvetica", sans-serif } /* reaches the margin boxes */
@page { @bottom-center { content: counter(page); font-size: 8pt } }
A margin box takes no room from the page content: it sits in the margin, and content taller than the margin overflows it. Make the margins large enough for the margin boxes.
Margin boxes in the output formats
- PDF: drawn on every page; in tagged PDF (PDF/UA, PDF/A-1a/2a/3a) they are page artifacts, as running headers should be.
- Images (
renderAsImages()): drawn as in PDF. - DOCX and RTF: the page size and margins are taken over into the document's sections; margin boxes are not (Word has its own header and footer model). Use
pd4ml:page.header/pd4ml:page.footerfor headers and footers that must appear in Word output too.
Media types
@page rules inside @media apply when the media type matches, like the rest of the style sheet. PD4ML applies screen (and all, pdf, pd4ml) rules by default, not print. A document that keeps its print styles in @media print { @page { ... } }, or in a <link media="print"> style sheet, needs print media switched on before readHTML():
pd4ml.setParam(Constants.PD4ML_MEDIA_TYPE_PRINT, "add"); // print rules as well as screen rules
// or "override": print rules instead of screen rules
pd4ml.readHTML(url);
Together with the PD4ML API
| API | With @page |
|---|---|
setPageSize(...) |
wins over size: a page size set through the API is used, the style sheet's is not. @page margins still apply. |
setPageMargins(...) |
wins over the @page margins. The @page size still applies. |
setHtmlWidth(...) |
scales the document to the content width of the first page, @page size and margins included. @page lengths stay physical. |
<pd4ml:page.break pageFormat="..."> |
sets the page size for the pages after it: from there on, @page sizes do not change it. @page margins still apply. |
setPageHeader(), setPageFooter(), pd4ml:page.header, pd4ml:page.footer |
work together with margin boxes: they take room from the content area, inside the @page margins. |
setInitialPageNumber(), <pd4ml:page.break pageCounter="..."> |
set the numbers counter(page) shows. |
layout() |
gives page sizes and element positions with the @page rules applied (see DOM API: Modifying the HTML Document Before Conversion). |
A style sheet added from Java code with the DOM API can carry @page rules too:
Element style = pd4ml.getDocument().createElement("style");
style.setTextContent("@page { size: letter; margin: 0.75in;"
+ " @bottom-right { content: counter(page) } }");
pd4ml.getDocument().getHead().appendChild(style);
Complete example
<!DOCTYPE html>
<html>
<head>
<style>
html { font: 10.5pt/1.4 "Georgia", serif }
@page {
size: A4;
margin: 25mm 20mm;
@top-left { content: "Quarterly Report"; font-size: 8.5pt; color: #555 }
@top-right { content: "Q3 2026"; font-size: 8.5pt; color: #555 }
@bottom-center { content: counter(page) " / " counter(pages); font-size: 8.5pt }
}
@page :first {
margin-top: 80mm;
@top-left { content: none }
@top-right { content: none }
}
@page :left { margin-left: 25mm; margin-right: 15mm }
@page :right { margin-left: 15mm; margin-right: 25mm }
@page appendix {
size: A4 landscape;
margin: 15mm;
@bottom-center { content: "Appendix - page " counter(page, upper-roman) }
}
.appendix { page: appendix }
table { width: 100%; border-collapse: collapse }
td, th { border: 1px solid #999; padding: 2pt 4pt }
</style>
</head>
<body>
<h1>Quarterly Report</h1>
<p>...</p>
<section class="appendix">
<h2>Appendix: figures by region</h2>
<table> ... a wide table, laid out across the landscape page ... </table>
</section>
<p>Closing remarks ...</p>
</body>
</html>
PD4ML pd4ml = new PD4ML();
pd4ml.useHtmlRenderer(Constants.LayoutEngine.v42);
pd4ml.readHTML(new File("report.html").toURI().toURL());
pd4ml.writePDF(new FileOutputStream("report.pdf")); // no page setup in code
Limitations
:first,:leftand:rightpages with other side margins than the first page keep the document's layout width (see above). Top and bottom margins have no such limit.- Named pages get a layout width of their own for elements directly in
<body>only. - Margin boxes show strings and page counters. Not supported:
width/heighton margin boxes,string()andstring-set, running elements (position: running(),element()),attr(),target-counter()and leaders. @pagerules of the v40 layout engine are limited tosizeand margins.
See also
- PDF Header and Footer Generation --
setPageHeader()/setPageFooter()andpd4ml:page.header/pd4ml:page.footer. - Dynamic Page Format Changes Within a Document --
<pd4ml:page.break pageFormat="...">. - DOM API: Modifying the HTML Document Before Conversion --
layout()included.
