Styling with CSS
Markdown and HTML documents are styled with the same CSS. Every property and its limits are listed in the CSS reference, and the Typography chapter explains the ones for type. This page covers how stylesheets get into a document and what belongs to the page as a whole.
Default stylesheet
Both modes start from the same built-in stylesheet:
- A4 page size with 2cm margins
- Serif font (CrimsonPro) for body text at 10pt
- Scaled heading sizes (h1: 24pt down to h6: 10pt italic)
- Monospace font (CamingoCode) for
codeandpreblocks - Table styling with header borders
- Blockquote indentation and italic style
Your own CSS layers on top of it and overrides matching rules.
Add your own CSS
| Source | Markdown | HTML | Relative paths resolve against |
|---|---|---|---|
css: in the frontmatter |
yes | no | the directory of the document |
--css flag |
yes | yes | the working directory |
<style>, style="" |
raw HTML | yes | |
<link rel="stylesheet"> |
raw HTML | yes | the directory of the document |
In Markdown:
---
css: style.css
---In HTML, the stylesheets belong to the document as usual, and the flag works the same in both:
glu --css style.css document.md
glu --css style.css page.htmlRules of the same specificity follow the usual order, and the last one wins:
- The default stylesheet
- The
css:file from the frontmatter - The stylesheets of the document itself, from
<style>and<link> - The
--cssfile
So the flag overrides what comes with the document, @page rules included. That makes it the way to adapt a finished document without touching it, for example for print:
glu --css print.css page.htmlPage size and margins
Use @page to change the page size and margins:
@page {
size: letter;
margin: 1in;
}
body {
font-size: 11pt;
line-height: 1.5;
}
h1 {
color: #333;
}In Markdown, the papersize field of the frontmatter is a shorthand for the page size.
Page margin boxes and counters
CSS @page rules support margin boxes for headers and footers (see also the headers and footers how-to). Inside margin boxes, use the content property with counter() to display page numbers:
@page {
size: a5;
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
font-size: 9pt;
color: #666;
}
}Available counters:
| Counter | Description |
|---|---|
page |
Current page number (1-based) |
pages |
Total number of pages |
The content property can mix literal strings and counter values freely: content: counter(page) " / " counter(pages);
Images can be placed in margin boxes using url():
@page {
@top-right {
content: url("logo.pdf");
}
}The image is scaled proportionally to fit the margin box height and aligned according to the box position (left, center, or right).
Use @page :first to override margin boxes on the first page:
@page :first {
@top-right {
content: ""; /* no logo on first page */
}
}Available margin boxes: top-left-corner, top-left, top-center, top-right, top-right-corner, bottom-left-corner, bottom-left, bottom-center, bottom-right, bottom-right-corner.
Auxiliary file
Forward references like counter(pages) (the total page count) and the table of contents cannot be resolved on the first typesetting pass, because they depend on values that aren’t known until the document is fully laid out. glu solves this with an auxiliary file and an automatic re-run loop:
For a source file document.md or document.html (or --output FILE if set), glu writes <stem>-aux.json, a formatted JSON file next to the PDF.
- Pass 1: glu typesets the document.
counter(pages)shows0because no previous data exists. After the pass, glu writes the actual page count, the collected headings and the anchors to the aux file. - Pass 2: glu runs another pass only when something the first pass read has changed: the page count if the document uses
counter(pages), or an anchor thattarget-counter()ortarget-text()looked up. When user Lua ran (the companion file, a{lua}block or an inline expression), any change of the aux data counts, since that code can read anything in_aux. The pass reads the new aux file, starts with a fresh Lua state, and typesets again. The forward references now resolve to real values. - Convergence: when nothing a pass read has changed, glu stops. A document without forward references is typeset once.
The loop is bounded by --max-passes (default 3). If the aux file oscillates between two different states across passes, typically a sign of non-deterministic Lua code, glu aborts the loop early with exit code 6 instead of wasting more passes. --max-passes=1 disables auto-rerun entirely.
Each pass runs with a fresh Lua state: the companion .lua file is re-loaded, {lua} blocks are re-run, lifecycle callbacks fire again. This is deliberate: accumulating state across passes would compound side effects (counters, registered callbacks, file writes).
Use --clean to delete the aux file before pass 1:
glu --clean document.mdCross-references
The two-pass loop is what makes CSS target-counter(attr(href), page),
target-counters(attr(href), chapter, ".") and
target-text(attr(href)) resolve to real page numbers, section
numbers and titles. The first pass renders the
cross-references as ? and collects {id → {page, text, counters}}
for every anchor; the second pass reads the aux entry back and emits
final values. See the
cross-references reference for the CSS
shape, and these two example folders for end-to-end setups in Markdown:
- toc-target-counter: table of contents with dotted leaders and resolved page numbers.
- cross-reference-inline: inline
<span id>anchors plustarget-textfor “see Chapter 3” references.
JSON format
The aux file uses _-prefixed keys for system data. User data can use any key without underscore prefix:
{
"_headings": [
{ "level": "h1", "text": "Introduction", "page": 1 },
{ "level": "h2", "text": "Summary", "page": 3 }
],
"_pages": 3,
"my_index": ["Alpha", "Beta"]
}| Key | Type | Description |
|---|---|---|
_pages |
number | Total page count (set by glu after each run) |
_headings |
array | Heading entries with level, text, and page |
_anchors |
object | One entry per id, read by target-counter() and target-text() |
| user keys | any | Custom data stored by Lua code |
Lua code reads and writes this data through the _aux table.
Leaders
Leaders create repeating dot patterns that fill available space, commonly used in table-of-contents entries. Use the CSS content: leader('.') property (from the CSS Generated Content for Paged Media specification) on an empty inline element:
<style>
.toc-entry { margin-top: 2pt; margin-bottom: 2pt; }
</style>
<p class="toc-entry">
Chapter 1<span style="content: leader('.')"></span>5
</p>
<p class="toc-entry">
Chapter 2<span style="content: leader('.')"></span>12
</p>This produces:
Chapter 1 . . . . . . . . . . . . 5
Chapter 2 . . . . . . . . . . . . 12The dots are vertically aligned across lines (global grid alignment). The leader pattern can be any string: leader('.') for dots, leader('_') for underscores, and so on.
The <span> must be empty (no child content). The content property is parsed from the inline style attribute.
In Markdown, write the <span> as raw HTML.