# Configuration: headers and footers

> What prints at the top and the foot of every page: text with placeholders, rules, boxes and images, by page parity, and where each element sits

- HTML version: https://postext.dev/en/docs/configuration-headers-footers
- Last updated: 2026-10-16
- Reading time: 29 min
- Other languages: [es](https://postext.dev/es/docs/configuration-headers-footers.md), [ca](https://postext.dev/ca/docs/configuration-headers-footers.md), [de](https://postext.dev/de/docs/configuration-headers-footers.md), [fr](https://postext.dev/fr/docs/configuration-headers-footers.md), [it](https://postext.dev/it/docs/configuration-headers-footers.md), [pt](https://postext.dev/pt/docs/configuration-headers-footers.md), [zh](https://postext.dev/zh/docs/configuration-headers-footers.md), [ja](https://postext.dev/ja/docs/configuration-headers-footers.md), [ar](https://postext.dev/ar/docs/configuration-headers-footers.md)

## In short

This page covers what is printed at the top and at the bottom of every page. A header or a footer can hold the page number, the title of the book or of the chapter, a line, a box or a small picture. Left and right pages can show different things. Each element can be placed at the left, in the middle or at the right, or in the margin beside the text. The header and the footer sit inside the margins, so they never make the text area smaller.

## Headers & footers

The `header` and `footer` properties control per-page header and footer slots. Headers and footers render **inside the existing page margins** — they do not reserve additional space and do not shrink the content area.

**Container frame.** An element anchored to `'container'` is placed in the margin band between the body and the trim edge, spanning the content-area width. The header container runs from the **trim top** down to the top of the body; the footer container from the bottom of the body down to the **trim bottom**. So `top-*` header anchors and `bottom-*` footer anchors measure from the trim edge, while `bottom-*` header anchors and `top-*` footer anchors measure from the body edge. The container never includes the bleed or the crop-marks band, so a header or footer lands at the same position on the trimmed page whether `page.cutLines` is on or off. Anchor to `'page'` (the trim box) or `'bleed'` to reach beyond the content-area width or into the bleed.

Slots use the unified **design slot** model: every element has a `placement` with an `anchor` (to the container or to another element by `#id`), an optional `offset`, and an optional `size`. The legacy flat fields `align`, `marginFromBody`, `marginFromEdge`, and `width: 'full'` are still accepted on input and are migrated to the new shape automatically; the equivalent new-shape description is documented below.

Each slot holds a list of **text**, **rule**, and **box** elements. Array order is paint order (first element paints first, last element paints on top). This holds whatever the anchors say: an element may anchor to one listed after it (`anchor.to: '#ttl'`), so a background box can come first and still be positioned against the text it sits behind.

**Built-in defaults.** When `header` or `footer` is `undefined`, postext applies a sensible built-in default rather than an empty slot:

- **Default header:** `{title}` right-aligned on odd pages, `{chapterTitle}` left-aligned on even pages, and a full-width rule — all in the palette's main color, Open Sans 8pt/600, `marginFromBody` `16pt` (text) / `13pt` (rule).
- **Default footer:** `{pageNumber}` centered on every page in the palette's main color, Open Sans 8pt/600, `marginFromBody` `16pt`.

To opt out of the built-in defaults, set `header: { elements: [] }` (or `footer: { elements: [] }`). An explicit empty `elements` array is preserved as "no elements" — only `undefined` triggers the defaults.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `elements` | `HeaderFooterElement[]` | built-in defaults when `undefined`; `[]` disables | Ordered list of text and rule elements. |

### Text elements

Text elements render a template string with placeholder substitution. Placeholders use `{name}` syntax; `{{` and `}}` emit literal braces.

The defaults in the table below are those of a text element you add yourself. The built-in header and footer described under **Built-in defaults** above are ready-made elements with their own values (Open Sans 8pt/600 in the palette's main colour), not the element defaults.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `kind` | `'text'` | — | Discriminator. |
| `id` | `string` | — | Stable id, unique within the slot. Other elements anchor to it with `anchor.to: '#id'`. The sandbox assigns one on creation. |
| `content` | `string` | `''` | Template string. Supports placeholders listed below, plus `{attr.<key>}` — an attribute written on the current chapter's H1 line (`# Title {author="I. Zango"}`). A missing attribute resolves to an empty string without a warning. A newline — or the two characters `\n`, written in the template or in an attribute value — always starts a new line, whatever the `overflow`. Text a placeholder copies from the document (a title, a frontmatter field) prints as written: there only a real line break, such as a title break, starts a new line. |
| `align` | `'left' \| 'center' \| 'right' \| 'justify' \| 'start' \| 'end'` | `'center'` | Horizontal alignment of the lines inside the element's box. `'justify'` stretches the word spaces of every wrapped line but the last of each paragraph so the line fills the box; the lines beside a drop cap fill the room beside it. With `hyphenate`, a word that does not fit the rest of a justified line is also cut at a syllable to fill it. The last line of a paragraph, a line with no space to stretch and a text that does not wrap are set flush left. Justified text is laid out paragraph by paragraph, so blank lines between paragraphs count as one. The canvas and the PDF place every word where the layout put it; the HTML widens the spaces with `word-spacing`. `'start'` and `'end'` follow the text's `direction`: the right and the left of a right-to-left text. `'left'` and `'right'` are the box's own sides; in a design laid out in the flow of a right-to-left page (an opener band, a heading design) the flow is mirrored, so they are its start and end, as in the body text. |
| `direction` | `'ltr' \| 'rtl' \| 'auto'` | the document's | Base direction of the text: where its neutral characters go, the order of its runs on a line and the sides `'start'` and `'end'` mean. `'auto'` reads the first strong letter of the resolved text and falls back to the document's [`direction`](https://postext.dev/en/docs/configuration-language.md#text-direction). Arabic and Hebrew runs read right to left whatever the base. A text that holds an Arabic letter is never letter-spaced (`letterSpacing` is ignored for the whole text) and its words are never cut while wrapping or truncating; a word wider than the box overflows and is reported as `unbreakableWordOverflow`. |
| `parity` | `'all' \| 'odd' \| 'even'` | `'all'` | Which pages the element appears on (page number parity: page 1 is odd). |
| `pages` | `'all' \| 'body' \| 'opener' \| 'part' \| 'blank'` | `'all'` | Which page *roles* the element appears on, combined with `parity`. After placement every page is classified as `'blank'` (parity / separator padding, or no content), `'part'` (a part-divider page), `'opener'` (its first block is a heading whose level spans the page or forces a page break before it — the first page of a chapter) or `'body'` (everything else). `pages: 'body'` hides a running head on chapter openers; `pages: 'opener'` shows a folio only there. |
| `fontFamily` | `string` | `'EB Garamond'` | Font family. |
| `fontSize` | `Dimension` | `8 pt` | Font size. |
| `fontWeight` | `number` | `400` | Font weight (100–900). |
| `italic` | `boolean` | `false` | Whether to render in italic. |
| `color` | `ColorValue` | `#000000` | Text colour. |
| `overflow` | `'wrap' \| 'ellipsis-start' \| 'ellipsis-middle' \| 'ellipsis-end' \| 'clip'` | `'wrap'` in heading and part designs, else `'ellipsis-end'` | How the engine handles text that exceeds the element's available width. `'wrap'` breaks the line into multiple lines; the ellipsis variants keep each line on one line and truncate it with `…` at the start, middle, or end; `'clip'` hard-clips to the element's bounding box without inserting any character. Line breaks in the content apply in every mode: the ellipsis and clip modes truncate or clip each line on its own. An element that leaves `overflow` out takes its slot's default: in a heading design (`advancedDesign.slot`) or on a part page (`parts.design`, `parts.versoDesign`) it wraps, in a running head, a folio or a contents part row (`toc.parts.design`, a row of fixed height) it ends in `…`. Set `'wrap'` on a running head that may run to several lines (an address), or an ellipsis on a kicker that must stay on one line in an opener. A line cut by an ellipsis, or clipped with ink past the box, is reported as a `designTextTruncated` content warning, once per element and page (a running head or a folio once per element and chapter). A configuration stored before postext 1.24 (`configVersion` 9 or older) is read with `'ellipsis-end'` on every heading and part text that sets none, so its pages do not move. A text with a `dropCap` wraps whatever this says; with `'clip'` its lines are still cut at the edge of a box of fixed height. `'ellipsis-end'` and `'ellipsis-start'` cut at a word boundary — *The history of…*, not *The history of th…* — and no space or joining punctuation (comma, colon, dash, slash, opening bracket) touches the ellipsis. A word is cut where it must only when the boundary, that punctuation dropped, would keep less than half of what fits: one long word, or a URL (*http://exampl…*, not *http…*). A no-break space or a no-break hyphen (U+2011, as in *MS‑DOS*) is not a word boundary; `'ellipsis-middle'` cuts anywhere but drops the spaces beside it. Up to postext 1.4 every mode cut at the last character that fitted, spaces included. |
| `verticalAlign` | `'top' \| 'middle' \| 'bottom'` | `'middle'` | Where the text sits inside a box taller than its lines — a fixed `placement.size.height`, or a box stretched by an anchored neighbour. When the lines are taller than the box (a large numeral in a box of fixed height, a tight `lineHeight`), they run out on the side the alignment leaves free, as CSS flex alignment does: `'bottom'` keeps the foot of the last line box on the foot of the box and runs out at the top, `'middle'` runs out evenly at both ends, `'top'` at the foot. Up to postext 1.4 such lines always hung from the top, whatever the alignment. A `dropCap` moves with its lines (up to postext 1.4 it stayed at the top of a box that `'middle'` or `'bottom'` moved them down in). |
| `lineHeight` | `number \| Dimension` | `1.2` | Leading of the element's lines. A number is a multiple of `fontSize`. A `Dimension` is accepted too, the way every other leading of the configuration is written: `em` / `rem` is the same multiple, and an absolute length (`pt`, `mm`, `px`…) is the distance between baselines — `{ value: 15.5, unit: 'pt' }` sets 15.5 pt leading whatever the size. Anything else (zero, a negative number, a malformed dimension) takes the default. Up to postext 1.4 a `Dimension` here made the design's height unmeasurable: an opener then reserved no room at all, not even its `minHeight`, and the body ran under the title. |
| `letterSpacing` | `Dimension` | `0` | Tracking: extra space advanced after every character, spaces included, exactly as CSS `letter-spacing` does. Measured widths grow with it, so an auto-width box stays tight. It never changes the glyphs: the face's ligatures stay on canvas, in HTML, in EPUB and in the PDF (up to postext 1.27 canvas and HTML dropped them from tracked design text, which came out wider than measured). A Hebrew run is tracked like Latin text; only a text with Arabic letters is not. The tracking after the last character of a line is left out of its alignment and of an auto-width box, so a centred tracked title is centred on its letters, a right-aligned one ends on the edge and the last letter of a justified line reaches the edge (up to postext 1.4 they sat half a tracking unit, or a whole one, to the left, and an element anchored to the right of a tracked one stood a tracking unit further off). A negative value tightens the letters — a display title at 36 pt often takes `{ value: -0.3, unit: 'pt' }` — and the widths shrink the same way; canvas, HTML and PDF paint it alike (up to postext 1.4 a negative value was set as `0`, with no warning). |
| `textTransform` | `'none' \| 'uppercase'` | `'none'` | Letter-case transform applied to the resolved text, placeholders included — a part title set in capitals in the contents. |
| `box` | `ElementBoxStyle` | — | Optional background and border drawn behind the text: `backgroundColor`, `borderColor`, `borderWidth`, `borderRadius` and a per-side `padding` that grows the box beyond the text (see [Box elements](https://postext.dev/en/docs/configuration-headers-footers.md#box-elements) for the fields). |
| `dropCap` | `{ lines, fontFamily, fontWeight, fontSize, color, gap }` | — | Drop cap: the first letter set large beside the first `lines` lines (default 2), in its own face, weight and colour, `gap` from the text. The letter stands on the baseline of the last line it spans, and `fontSize` defaults to the size that brings its top level with the capitals of the first line: the text size plus `lines` − 1 line spacings, capitals taken as 0.72 of a letter's size (a face whose capitals are much taller or shorter than that wants a `fontSize` of its own). A text with a drop cap wraps whatever its `overflow`; with `'clip'` its lines are still cut at the edge of a box of fixed height. A palette-linked `color` follows the part and section palettes like the rest of the design. In a heading design the letter reserves no room below the text: the part of its line box under its baseline does not push the body down. A letter that descends below its baseline (a Q or a J in many faces) can then reach into the space under the design: give the heading a `marginBottom` for it. Up to postext 1.4 the default size made the letter as tall as all the line boxes it spans, so its top stood above the first line; an `overflow` other than `'wrap'` dropped the letter without a warning; a section or part palette left its colour as it was; and a letter as deep as the text beside it could push the body a grid line lower. Configurations stored earlier keep the 1.4 size, written out as `fontSize` (see [Bundles written by postext 1.4 or earlier](https://postext.dev/en/docs/configuration-bundles.md#bundles-written-by-postext-14-or-earlier)). A drop cap in the running text is set from the paragraph and heading styles instead (see [Drop caps](https://postext.dev/en/docs/configuration-text.md#drop-caps)), its cap heights measured from the faces. |
| `paragraphIndent` | `Dimension` | `0` | First-line indent of every paragraph after the first. A newline in the content — or the two characters `\n`, for text that comes from an attribute value — separates paragraphs; consecutive newlines count as one. |
| `hyphenate` | `boolean` | `false` | When `true` and the text wraps (`overflow: 'wrap'`, or a `dropCap`, which always wraps), long words that would still overflow after a regular line break are split at syllable boundaries (using the document's active hyphenation locale) with a soft-hyphen at the break. In a justified text (`align: 'justify'`) a word that does not fit the rest of a line is also cut at its last syllable break that fits, to fill the line. |
| `inlineMarks` | `boolean` | `false` | Read the resolved text — placeholder values included — as inline Markdown: `**bold**`, `*italic*`, `^superscript^`, `~subscript~`. Off, the markers print as written. In a heading design, `{titleText}` then keeps the heading's own bold, italic, superscript and subscript runs (`*Pneumocystis*`, `CO~2~`), its other characters escaped so they print as written (since postext 1.19). See [Inline marks and outlines](https://postext.dev/en/docs/configuration-headers-footers.md#inline-marks-and-outlines). |
| `stroke` | `{ width, color?, hollow? }` | — | Outline drawn around the letters: `width` (a `Dimension`, centred on the glyph edges), `color` (default: the text colour; a drop cap takes its own colour) and `hollow` (`true` paints the outline only). See [Inline marks and outlines](https://postext.dev/en/docs/configuration-headers-footers.md#inline-marks-and-outlines). |
| `writingMode` | `'horizontal-tb' \| 'vertical-rl'` | `'horizontal-tb'` | `'vertical-rl'` sets the text top to bottom, lines right to left, the characters upright: a running head down the fore-edge, a vertical title beside a horizontal chapter. See [Vertical text elements](https://postext.dev/en/docs/configuration-headers-footers.md#vertical-text-elements). |
| `reserve` | `boolean` | `true` | Heading designs only: whether the element counts toward the height the heading reserves in the text flow. `false` for decoration that may lie under the text (a seal at the page foot, a frame, a side band). See [Reserved height](https://postext.dev/en/docs/configuration-headings.md#reserved-height). Header, footer and part designs ignore it. |
| `marginFromBody` | `Dimension` | `6 pt` | Absolute distance between the element's body-facing edge and the body edge. Independent of other elements. Migrated to `placement.offset.y`. |
| `marginFromEdge` | `Dimension` | `0 pt` | Horizontal inset from the aligned content edge. Only applies when `align` is `'left'` or `'right'`. Migrated to `placement.offset.x`. |
| `placement` | `ElementPlacement` | derived from `align` + `marginFromBody` + `marginFromEdge` | Advanced placement (see below). When set, takes precedence over the legacy flat fields. |

Available placeholders:

- `{pageNumber}` — 1-based page number of the current page.
- `{totalPages}` — total page count for the document. In a book laid out chapter by chapter (the Sandbox, `buildBundle`) each chapter is a document, so this is the chapter's own count.
- `{bookTotalPages}` — total page count for the whole book: every chapter, blank pages included. For a document laid out on its own it equals `{totalPages}`. See [Book page count](https://postext.dev/en/docs/configuration-headers-footers.md#book-page-count) below.
- `{title}`, `{subtitle}`, `{author}`, `{publishDate}` — values read from `content.metadata`. Unknown or empty metadata renders as an empty string (and raises a warning in the sandbox).
- `{chapterTitle}` — text of the most recent H1 on or before the current page. An H1 whose [heading style](https://postext.dev/en/docs/configuration-styles.md#heading-styles) sets `runningChapter: false` (a plate, a map) is passed over.
- `{chapterTitleAtTop}`, `{chapterNumberAtTop}` — title and number of the chapter in force at the top of the page, which differs from `{chapterTitle}` and `{chapterNumber}` on a page where a new chapter starts below other text. See [Chapter at the top of the page](https://postext.dev/en/docs/configuration-headers-footers.md#chapter-at-the-top-of-the-page) below.
- `{partTitle}`, `{partNumber}` — title and number of the current part (the most recent `:::part` page on or before the current page; blank parity pages just before a part page already belong to it). Empty before the first part.
- `{firstMark.<key>}`, `{lastMark.<key>}` — the first and last guide word of the page: a heading of a level (`h1`–`h6`) or an entry of a paragraph style. See [Guide words](https://postext.dev/en/docs/configuration-headers-footers.md#guide-words-first-and-last-mark) below.

#### Book page count

`{bookTotalPages}` prints the number of pages of the whole book, the count a reader sees in "page 12 of 348". It counts physical pages, blank ones included, like `{totalPages}`, but across every chapter:

- **A document laid out on its own** (`buildDocument` without a `continuation`) is the whole book: `{bookTotalPages}` equals `{totalPages}`.
- **`buildBundle`** lays the book out, adds up the pages of every chapter and lays it out once more with that total, so every chapter prints the same number. The count never moves a page break, so one more round settles it; a configuration that does not print `{bookTotalPages}` costs nothing.
- **The Sandbox** hands every chapter the total once every chapter's pages are known. Until then, a chapter prints the pages up to its own end. The PDF tab's whole-book export counts the pages it lays out: when a chapter printed another count, because its pages were not known yet, it lays the book out once more with the count the chapters came to.
- **A host laying chapters out itself** passes the total as `continuation.bookPageCount` (the first chapter too). Without it, `{bookTotalPages}` counts the pages up to the end of the document (`continuation.pageIndexOffset` plus its own pages), which is right for the last chapter only.

```ts
footer: {
  elements: [{
    kind: 'text', id: 'folio', content: '{pageNumber} / {bookTotalPages}',
    fontSize: { value: 8, unit: 'pt' },
    placement: { anchor: { to: 'container', edge: 'top' }, size: { width: 'auto', height: 'auto' } },
  }],
}
```

`configUsesPlaceholder(config, 'bookTotalPages')` tells a host whether the count is worth computing.

#### Guide words: first and last mark

A dictionary prints the first and last headword of each page in its running head ("Aback – Anchor"); a reference book prints the first and last section. `{firstMark.<key>}` and `{lastMark.<key>}` print them. The key names what marks a page:

- **`h1` to `h6`**: a heading of that level. The mark is its text, without the number.
- **A paragraph style id** (`entry`): a paragraph of a `:::paragraphs{style="entry"}` container. The mark is the paragraph's opening bold run, the headword, without its closing punctuation: `**Aback.** Said of…` marks `Aback`. A paragraph that does not open with bold text sets no mark.

`{firstMark.<key>}` is the first mark that starts on the page and `{lastMark.<key>}` the last. A page on which no mark starts (a long entry running on) prints the mark in effect, the last one before it, for both. Pages before the first mark print nothing; in a book laid out chapter by chapter that is the chapter's first mark, since marks do not carry over from one chapter to the next. A heading or paragraph split across pages marks the page it starts on only. The key is written after the dot as letters, digits, `_` and `-`, starting with a letter or `_`; an unknown key prints nothing.

```md
:::paragraphs{style="entry"}
**Aback.** Said of the sails when pressed back against the mast.

**Abaft.** Towards the stern, or behind a given point.
:::
```

```ts
header: {
  elements: [{
    kind: 'text', id: 'guide', content: '{firstMark.entry} – {lastMark.entry}',
    fontSize: { value: 8, unit: 'pt' },
    placement: { anchor: { to: 'container', edge: 'bottom' }, size: { width: 'auto', height: 'auto' } },
  }],
}
```

Guide words are running heads: they resolve in header and footer slots (a heading style's `header` and `footer` included) and print nothing in heading, part and contents designs; in heading and part designs the Checks panel flags them as unknown placeholders. To show the first headword on versos and the last on rectos, give two elements `parity: 'even'` and `parity: 'odd'`. An H1 whose heading style sets `runningChapter: false` sets no `h1` mark.

#### Chapter at the top of the page

`{chapterTitle}` and `{chapterNumber}` name the last chapter begun on or before the page. In a book whose chapters run on, without a page break between them, a page that closes one chapter and starts the next near its foot then carries the new chapter's title over text that still belongs to the old one. `{chapterTitleAtTop}` and `{chapterNumberAtTop}` name the chapter in force at the top of the page instead, as novels with run-on chapters and many reference books do:

- a page whose first block is a chapter's H1 names that chapter;
- any other page names the chapter it runs on from, even when a new chapter starts lower down;
- a blank page added for parity goes with the page after it, and the separator of the `always-*` modes with the page before it (see [Blank-page ownership](https://postext.dev/en/docs/configuration-headings.md#blank-page-ownership)); when the page after a parity blank opens with the end of a chapter, not with its H1, the blank keeps that chapter too;
- an H1 with `runningChapter: false` is passed over.

```ts
header: {
  elements: [{
    kind: 'text', id: 'chapter', content: '{chapterTitleAtTop}', parity: 'odd', pages: 'body',
    fontSize: { value: 8, unit: 'pt' },
    placement: { anchor: { to: 'container', edge: 'bottom-right' }, size: { width: 'auto', height: 'auto' } },
  }],
}
```

Like guide words, both are running heads: they resolve in header and footer slots and print nothing in heading, part and contents designs. `{attr.<key>}` always reads the last chapter begun.

#### Edge-implied alignment

When a text element's `placement.anchor.to` references another element by `#id`, the anchor edge implies a default text alignment for wrapped lines:

- `right-of` and `align-left` imply text `align: 'left'` — wrapped lines flow rightward from the anchor.
- `left-of` and `align-right` imply `align: 'right'` — wrapped lines hug the side closest to the anchor target.

The sandbox heading editor applies these implied alignments automatically when you change the anchor edge or target. They keep multi-line wrapped text visually anchored to the element it relates to (so e.g. the "P" of a wrapped "Postext" lines up vertically under the "I" of "Introduction").

#### Inline marks and outlines

A text element sets its text in one face by default: `**`, `^` and the other Markdown markers print as written. With `inlineMarks: true` the resolved text is read as inline Markdown, the same marks the body text takes (see **Document format → Inline formatting**):

- `**bold**` sets weight 700 (or the element's own `fontWeight` when it is heavier); `*italic*` flips the element's slant, so emphasis inside an italic element comes out upright; `***both***` does both. The underscore forms (`__bold__`, `_italic_`) work too.
- `^superscript^` and `~subscript~` are set at 58% of the size, a superscript raised a third of it and a subscript lowered 0.15 of it; a subscript and a superscript that touch (`T~0~^2^`) are stacked, as in the body.
- A backslash sets a marker character itself (`\*`, `\_`, `\^`, `\~`). A link keeps its text; code backticks are dropped.

Marks are read after the placeholders are filled in, so a value can carry them: an author line in a heading attribute gets its affiliation numbers as superscripts. Wrapping, justification, the ellipsis modes, `dropCap` and `paragraphIndent` all work on marked text; every run keeps the element's colour.

```json
{ "kind": "text", "id": "authors", "content": "{attr.authors}", "inlineMarks": true, "overflow": "wrap",
  "fontSize": { "value": 11, "unit": "pt" },
  "placement": { "anchor": { "to": "#title", "edge": "below" }, "offset": { "y": { "value": 6, "unit": "pt" } } } }
```

```md
# Snow cover and river flow {authors="Ana Ruiz^1^, Luis Gil^2^ and Marta Sanz^1,3^"}
```

`stroke` draws an outline around the letters: `width` is the line width, centred on the glyph edges (half of it falls inside the letters, half outside; the measured text width does not change), `color` defaults to the text colour, and `hollow: true` leaves the letters unfilled so only the outline shows — an outlined display number, a title that stands off a photograph. The outline is painted over the fill, the same way on canvas, in HTML (`-webkit-text-stroke`) and in the PDF (text render mode 2, or 1 when hollow).

```json
{ "kind": "text", "id": "year", "content": "1863", "fontFamily": "Bitter", "fontSize": { "value": 120, "unit": "pt" }, "fontWeight": 700,
  "color": { "hex": "#1d3557", "model": "hex" },
  "stroke": { "width": { "value": 1.5, "unit": "pt" }, "hollow": true },
  "placement": { "anchor": { "to": "page", "edge": "bottom-right" }, "offset": { "x": { "value": -15, "unit": "mm" }, "y": { "value": -20, "unit": "mm" } } } }
```

In the PDF, bold and italic runs embed the matching faces of the element's family, so the font provider must supply them.

#### Text defaults and anchoring traps

A text element you write yourself starts from these values, some of which surprise:

- **`overflow` follows the slot.** In a heading design or on a part page a long title wraps onto more lines; in a running head, a folio or a contents part row a text too wide for its room is cut to one line with `…`. Set `overflow: 'wrap'` on a running head that may run long (an address, a byline), and look at the `designTextTruncated` warnings for the texts that were cut. A line break in the content (a newline, or `\n` in the template or in an attribute value) starts a new line in every mode; the ellipsis modes cut each line on its own.
- **`align` is `'center'` and `verticalAlign` is `'middle'`.** An auto-width element shrink-wraps its longest line, so its lines are centred against one another; set `align: 'left'` for a flush-left block (an auto-width element anchored to another element aligns its lines on the side of its anchor by itself, see above).
- **The face is EB Garamond 8 pt, black, `lineHeight` 1.2**, whatever the body text uses. Unlike the other leadings of the configuration, a design text's `lineHeight` is usually a plain multiple (`1.2`); a `Dimension` works too (see above).

An element with no `placement.size.width` (or `'auto'`) sizes itself to its text, but only within the room between its anchor point and the edge of the container it grows toward — for a `top` or `bottom` anchor, twice the distance to the nearer edge. Its `offset` counts. An offset away from that edge costs nothing: a running head anchored `top-left` with a negative `x` (hanging into the left margin) loses no room, because it grows to the right. An offset toward that edge shrinks the room by as much (twice as much for a `top` or `bottom` anchor), and one that pushes the anchor past the edge leaves none: a `top-right` anchor whose `x` is below minus the container width, or a `top` anchor moved sideways by more than half that width. With no room left, an ellipsis mode prints nothing and `'wrap'` stacks one character per line. Three ways out:

- give the element a fixed `size.width` — fixed widths are never clamped;
- anchor it to `'page'` or `'bleed'`, which makes the page (or bleed) frame its room;
- anchor it to the opposite edge of the container.

The container of a header runs from the trim top down to the body, and that of a footer from the body down to the trim bottom: in a header, `top-*` anchors measure from the trim edge and `bottom-*` anchors from the body, and the other way round in a footer (see **Container frame** above). A header or footer never moves the body text, and it paints on top of it, so an element pushed into the body area covers the text. An opener band, by contrast, paints under the body text; how much room it takes in the flow is described under [Reserved height](https://postext.dev/en/docs/configuration-headings.md#reserved-height).

### Vertical text elements

A text element with `writingMode: 'vertical-rl'` is set vertically in a slot whose text is horizontal: the running heads and folios of any book, which stay on the sheet, and every design of a horizontal page. It is laid out as a horizontal text in its own frame turned a quarter turn clockwise, and turned back onto the page:

- Its box stays where the placement puts it. Its height is the length of a line: `size.height` sets it (or `'auto'`, the text's length; `'fill'`, down to the container's edge), `size.width` sets how many lines fit across, and `size.maxWidth` caps a line's length.
- `align` places the lines along the box (`'left'` at the top), `verticalAlign` across it (`'top'` at the right, where the first line stands), and a box's padding stays on the side it is written for.
- The characters are measured and painted as in a vertical page: Han upright one em each, punctuation in its vertical form, Latin words sideways, short numbers in one cell (`cjk.uprightDigits`). The canvas, the PDF and the HTML place it at the same rectangle.
- With `inlineMarks: true` the orientation marks set a run apart as in the body: `第:tcy[3.0]回` sets 3.0 in one cell, `:upright[GDP]` stands the letters upright one under the other, `:sideways[…]` turns a run; a line never breaks inside one. A horizontal element ignores them.
- A drop cap is not set on a vertical element.
- In the flow of a vertical page (an opener, a part page, a box title) text already runs down, and `writingMode` changes nothing there.

Vertical Chinese books put their running heads and folios in one of three places ([clreq §7.2](https://www.w3.org/TR/clreq/#x7-2-page-headers-footers-etc); [JLREQ §2.6](https://www.w3.org/TR/jlreq/#running_heads_and_page_numbers) for Japanese):

| Convention | Where | How to set it |
| --- | --- | --- |
| Horizontal head and foot | Above and under the type area, as in horizontal books; the most common. | The header and footer as they are. |
| Fore-edge (中缝 style, Taiwan 邊峰) | Down the outer margin: the chapter or book title from about four characters below the head of the type area, the folio ending about five above its foot, in Chinese numerals, at about 80 % of the body size. | Two vertical elements anchored to `'outer'`, below. |
| Outer foot corner | The folio at the foot of the page, in the outer corner (Taiwan's rules for 中式 books). | A horizontal element in the footer at `'bottom-left'` with `parity: 'odd'` and one at `'bottom-right'` with `parity: 'even'` in a right-bound book (the other way round in a left-bound one). |

The fore-edge heads, as the Sandbox adds them (**Header › Fore-edge heads (vertical)**), here for a 10 pt body (the Sandbox sets them at 80 % of the body size):

```json
{
  "page": { "pageNumbering": { "format": "trad-chinese-informal" } },
  "header": { "elements": [
    { "kind": "text", "id": "head", "content": "{chapterTitle}", "writingMode": "vertical-rl",
      "fontSize": { "value": 8, "unit": "pt" }, "overflow": "clip", "align": "left",
      "placement": { "anchor": { "to": "outer", "edge": "top" }, "offset": { "y": { "value": 4, "unit": "em" } } } },
    { "kind": "text", "id": "folio", "content": "{pageNumber}", "writingMode": "vertical-rl",
      "fontSize": { "value": 8, "unit": "pt" }, "overflow": "clip", "align": "left",
      "placement": { "anchor": { "to": "outer", "edge": "bottom" }, "offset": { "y": { "value": -5, "unit": "em" } } } }
  ] }
}
```

`anchor.to: 'outer'` (see [Element placement](https://postext.dev/en/docs/configuration-headers-footers.md#element-placement)) is the outer margin of each page, so the two elements run down the left edge of a recto and the right edge of a verso in a right-bound book (`page.binding`). `{pageNumber}` prints in the page numbering format: `trad-chinese-informal` gives 一百零三 on page 103, `cjk-decimal` 一〇三. The per-slot rules still apply: `pages: 'body'` keeps a head off the chapter openers. A short ornament between head and folio (a fish-tail ︻, a rule) is an ordinary element anchored to the same frame.

In the VDT a vertical block carries `vertical` (`VDTDesignTextBlock.vertical`: the region, the upright digits and the central axis of each family); its lines are in the block's own turned frame, `xOffset` down from the box's top, `baselineY` leftward from its right edge. A run of a marked line carries `tcy` or `orientation`, as a segment of the body does.

### Rule elements

Rule elements render a line: horizontal across the slot, or vertical down it.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `kind` | `'rule'` | — | Discriminator. |
| `id` | `string` | — | Stable id, unique within the slot, for `anchor.to: '#id'` references. |
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | A horizontal rule runs along `placement.size.width` (`'fill'` = to the container edge) and is `thickness` tall. A vertical rule runs down `placement.size.height` (`'fill'` or unset = to the container edge) and is `thickness` wide — a divider between a running head and a folio. |
| `color` | `ColorValue` | `#000000` | Stroke colour. |
| `thickness` | `Dimension` | `0.5 pt` | Line thickness. A rule that leaves it out is drawn at the default (up to postext 1.4 it painted nothing). |
| `width` | `Dimension \| 'full'` | `'full'` | `'full'` spans the content area; a Dimension constrains the line to a fixed length positioned by `align`. |
| `align` | `'left' \| 'center' \| 'right'` | `'center'` | Alignment when `width` is not `'full'`. |
| `marginFromBody` | `Dimension` | `6 pt` | Absolute distance between the rule's body-facing edge and the body edge. Independent of other elements. |
| `marginFromEdge` | `Dimension` | `0 pt` | Horizontal inset from the aligned content edge. Only applies when `width` is a fixed `Dimension` and `align` is `'left'` or `'right'`. |
| `parity` | `'all' \| 'odd' \| 'even'` | `'all'` | Which pages the rule appears on. |
| `pages` | `'all' \| 'body' \| 'opener' \| 'part' \| 'blank'` | `'all'` | Page roles the rule appears on (see the text-element `pages` field). |
| `reserve` | `boolean` | `true` | Heading designs only: whether the rule counts toward the height the heading reserves in the text flow (see [Reserved height](https://postext.dev/en/docs/configuration-headings.md#reserved-height)). |
| `placement` | `ElementPlacement` | derived from `align` + `marginFromBody` + `marginFromEdge` | Advanced placement (see [Element placement](https://postext.dev/en/docs/configuration-headers-footers.md#element-placement)). `size.width` / `size.height` set the rule length; `width: 'fill'` is the legacy `'full'`. |

### Box elements

Box elements paint a rounded rectangle inside the slot — useful as a backdrop behind text in chapter openers, sidebars or footers. Box elements are positioned exclusively through the `placement` field; they have no legacy flat shorthand. Fill, stroke and corner radius live in the nested `style` object (`ElementBoxStyle`), as in the JSON example under "Element placement".

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `kind` | `'box'` | — | Discriminator. |
| `id` | `string` | — | Stable id, unique within the slot. Sibling elements anchor to it with `anchor.to: '#id'`. The sandbox assigns one on creation. |
| `style.backgroundColor` | `ColorValue` | `transparent` | Fill colour. Set to `transparent` for an outline-only box. |
| `style.borderColor` | `ColorValue` | `transparent` | Stroke colour. |
| `style.borderWidth` | `Dimension` | `0 pt` | Stroke width. Strokes are painted on the inside of the box's bounding rectangle so the outer dimensions stay constant: the outer edge of the stroke runs along the box edge, and a rounded box keeps its outer radius. A stroke as wide as the box fills it. Canvas, HTML and PDF draw it alike (up to postext 1.4 the canvas and the PDF centred the stroke on the edge, half of it outside the box). |
| `style.borderRadius` | `Dimension` | `0 pt` | Corner radius. Clamped to half the smaller side at render time. |
| `placement` | `ElementPlacement` | — | Required. See "Element placement" below. |
| `parity` | `'all' \| 'odd' \| 'even'` | `'all'` | Which pages the box appears on. |
| `pages` | `'all' \| 'body' \| 'opener' \| 'part' \| 'blank'` | `'all'` | Page roles the box appears on (see the text-element `pages` field). |
| `reserve` | `boolean` | `true` | Heading designs only: whether the box counts toward the height the heading reserves in the text flow (see [Reserved height](https://postext.dev/en/docs/configuration-headings.md#reserved-height)). |

### Image elements

An `image` element draws a bitmap or SVG resource of the document — a publisher's logo on a title page, a mark in a running head. It is sized by its `placement.size`: with one of `width` / `height` left `'auto'` (the default) the other side follows the image's aspect ratio; with both set the image is fitted inside the box and centred. A missing or non-image resource draws nothing.

```ts
{
  kind: 'image', id: 'logo', resourceId: 'logo-publisher',
  placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: { value: 64, unit: 'mm' }, y: { value: 233, unit: 'mm' } }, size: { width: { value: 83, unit: 'mm' }, height: 'auto' } },
}
```

`resourceId` takes the placeholders a text element's `content` takes, so one design can draw a different picture for each heading. With `resourceId: '{attr.vignette}'` in a heading style, `# Chapter I {style="opener" vignette="log"}` draws the resource `log` and `# Chapter II {style="opener" vignette="wig"}` draws `wig`: the chapters share the style instead of one clone per picture. A header or footer reads the attributes of the page's chapter, as `{attr.<key>}` does in a running head, and other placeholders work too (`'map-{chapterNumber}'`). An id that comes out empty, such as a heading without the attribute, draws nothing. Since postext 1.8.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | — | Stable identifier; other elements may anchor to it as `#id`. |
| `resourceId` | `string` | — | Id of a bitmap or SVG `Resource` of the document. It may hold placeholders, `{attr.<key>}` among them, filled in per heading, part or page. |
| `decorative` | `boolean` | `false` | The picture is decoration only (an ornament, a band): it gives no alternative text to the output even when its resource has one (see below). |
| `placement` | `ElementPlacement` | — | Anchor, offset and size (see [Element placement](https://postext.dev/en/docs/configuration-headers-footers.md#element-placement)). A `'fill'` side runs to the container edge. |
| `parity`, `pages` | as above | `'all'` | Which pages the image appears on. |
| `reserve` | `boolean` | `true` | Heading designs only: whether the image counts toward the height the heading reserves in the text flow (see [Reserved height](https://postext.dev/en/docs/configuration-headings.md#reserved-height)). |

The PDF backend embeds the resource like a figure (an SVG with a print master uses it); the HTML viewer resolves it through `resourceImageUrl`.

A picture a design draws is content when its resource describes it: the resource's `altText`, else its caption as plain text (a chip read by its label, a `:ref` by its `text` when it has one), goes into the VDT (`VDTDesignImageBlock.altText`), becomes the `alt` of its `<img>` in HTML and a `Figure` with `/Alt` in a tagged PDF, read right after the text of its design (a chapter's plate after the chapter's heading). A picture whose resource has neither, and one marked `decorative`, is decoration: `alt=""` with `role="presentation"`, and an artifact in the PDF. A running head's or footer's picture repeats on every page, so it is furniture whatever its resource says: no `altText` in the VDT, `alt=""` with `role="presentation"` in HTML, an artifact of the page in the PDF.

### Element placement

`ElementPlacement` is the unified positioning model used by every element type (text, rule, box) inside any design slot — page header, page footer, or heading-level advanced design slot. Three pieces of state describe a placement:

```ts
interface ElementPlacement {
  /** What this element anchors to and which edge of that target. */
  anchor: {
    to: 'container' | 'page' | 'bleed' | 'outer' | `#${string}`; // container = the slot; page = trim box; bleed = trim box + bleed; outer = the outer margin (header, footer); #id = another element
    edge: AnchorEdge;
  };
  /** Distance from the anchor point. */
  offset?: { x?: Dimension; y?: Dimension };
  /** Optional fixed width / height. Width also accepts 'fill' (span the slot).
   *  `maxWidth` caps an 'auto' width (text): the element still shrink-wraps its
   *  content, so elements anchored to it stay attached, but a long text wraps or
   *  ellipsizes there — a running head can reserve room for the label hanging
   *  off it instead of squeezing that label out. */
  size?: { width?: Dimension | 'fill' | 'auto'; height?: Dimension | 'fill' | 'auto'; maxWidth?: Dimension };
}
```

`AnchorEdge` accepts:

- **Container edges** (when `anchor.to` is `'container'`, `'page'` or `'bleed'`): `top`, `top-left`, `top-right`, `bottom`, `bottom-left`, `bottom-right`, `left`, `right`.
- **Element-relative edges** (when `anchor.to === '#someId'`): `right-of`, `left-of`, `below`, `above`, `align-top`, `align-bottom`, `align-left`, `align-right`.

Each element-relative edge puts one corner of the element on a corner of the element it anchors to, and `offset` moves it from there:

- `right-of`: its top-left corner on the target's top-right corner (beside it, tops level); `left-of`: its top-right corner on the target's top-left corner;
- `below`: its top-left corner on the target's bottom-left corner (under it, left edges level); `above`: its bottom-left corner on the target's top-left corner;
- `align-top` and `align-left`: its top-left corner on the target's top-left corner. The two names give the same placement: both the top and the left edges line up;
- `align-bottom`: its bottom-left corner on the target's bottom-left corner;
- `align-right`: its top-right corner on the target's top-right corner.

An element-relative edge used with `'container'`, `'page'` or `'bleed'`, and a container edge used with `'#id'`, are read as the top-left corner.

`anchor.to: 'page'` anchors the element to the **trim box** (the physical page after cutting) and `'bleed'` to the trim box grown by `cutLines.bleed` on every side (identical to the trim box while cut lines are disabled). Both frames also become the reference for `size: 'fill'` and for the automatic width clamp, so a coloured band can run edge to edge regardless of the page margins:

```json
{ "kind": "box", "id": "band", "placement": { "anchor": { "to": "bleed", "edge": "top-left" }, "size": { "width": "fill", "height": { "value": 6, "unit": "cm" } } }, "style": { "backgroundColor": { "hex": "#1d3557", "model": "hex" } } }
```

With cut lines on, whatever an element paints past the bleed box is clipped (see [Cut lines](https://postext.dev/en/docs/configuration-page-layout.md#cut-lines)).

`anchor.to: 'outer'` (header and footer slots) anchors the element to the page's **outer margin**: from the edge of the type area to the trim edge on the side away from the spine, and from the head of the type area to its foot. It is on the right of a recto and the left of a verso in a left-bound book and the other way round in a right-bound one (`page.binding`), so one element serves both pages of a spread: a running head down the fore-edge (see [Vertical text elements](https://postext.dev/en/docs/configuration-headers-footers.md#vertical-text-elements)). In any other slot it reads as `'container'`. A text element's `offset` may be written in `em`, ems of its own `fontSize`: four characters below the head of the type area is `{ "y": { "value": 4, "unit": "em" } }`.

Inside a heading's advanced-design slot, page- and bleed-anchored elements do not grow the height reserved for the heading unless they extend below the heading's top edge (a band across the top of the page sits behind the opener; a band reaching below the heading pushes the body text down). Use `advancedDesign.minHeight` to reserve a fixed opener height regardless, and `reserve: false` on an element that should not push the text at all. The full rules are under [Reserved height](https://postext.dev/en/docs/configuration-headings.md#reserved-height).

Each element has a stable `id` (auto-assigned by the sandbox; you can also set it by hand). Elements anchored to other elements form a small dependency graph that the engine resolves before measuring, so an element can chain off another without manual coordinates.

The legacy `align` + `marginFromBody` + `marginFromEdge` shape is parsed on input and rewritten into a placement at config-resolution time, so existing configs keep working unchanged.
