Skip to main content

Chapter 3.2 · Part II · The craft

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

Updated 2026-10-1629 minIn chapter 3 Configurationenescadefritptzhjaar

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.

PropertyTypeDefaultDescription
elementsHeaderFooterElement[]built-in defaults when undefined; [] disablesOrdered 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.

PropertyTypeDefaultDescription
kind'text'—Discriminator.
idstring—Stable id, unique within the slot. Other elements anchor to it with anchor.to: '#id'. The sandbox assigns one on creation.
contentstring''Template string. Supports placeholders listed below, plus — an attribute written on the current chapter's H1 line (# Title ). 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'sBase 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. 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.
fontFamilystring'EB Garamond'Font family.
fontSizeDimension8 ptFont size.
fontWeightnumber400Font weight (100–900).
italicbooleanfalseWhether to render in italic.
colorColorValue#000000Text 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).
lineHeightnumber | Dimension1.2Leading 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 — 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.
letterSpacingDimension0Tracking: 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.
boxElementBoxStyle—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 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). A drop cap in the running text is set from the paragraph and heading styles instead (see Drop caps), its cap heights measured from the faces.
paragraphIndentDimension0First-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.
hyphenatebooleanfalseWhen 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.
inlineMarksbooleanfalseRead the resolved text — placeholder values included — as inline Markdown: bold, italic, ^superscript^, ~subscript~. Off, the markers print as written. In a heading design, 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.
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.
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.
reservebooleantrueHeading 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. Header, footer and part designs ignore it.
marginFromBodyDimension6 ptAbsolute distance between the element's body-facing edge and the body edge. Independent of other elements. Migrated to placement.offset.y.
marginFromEdgeDimension0 ptHorizontal inset from the aligned content edge. Only applies when align is 'left' or 'right'. Migrated to placement.offset.x.
placementElementPlacementderived from align + marginFromBody + marginFromEdgeAdvanced 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 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 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 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 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.
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.

:::paragraphs{style="entry"}
**Aback.** Said of the sails when pressed back against the mast.
 
**Abaft.** Towards the stern, or behind a given point.
:::
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); 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.
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.

{ "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" } } } }
# 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).

{ "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.

#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; JLREQ §2.6 for Japanese):

ConventionWhereHow to set it
Horizontal head and footAbove 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 cornerThe 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):

{
  "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) 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.

PropertyTypeDefaultDescription
kind'rule'—Discriminator.
idstring—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.
colorColorValue#000000Stroke colour.
thicknessDimension0.5 ptLine thickness. A rule that leaves it out is drawn at the default (up to postext 1.4 it painted nothing).
widthDimension | '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'.
marginFromBodyDimension6 ptAbsolute distance between the rule's body-facing edge and the body edge. Independent of other elements.
marginFromEdgeDimension0 ptHorizontal 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).
reservebooleantrueHeading designs only: whether the rule counts toward the height the heading reserves in the text flow (see Reserved height).
placementElementPlacementderived from align + marginFromBody + marginFromEdgeAdvanced placement (see 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".

PropertyTypeDefaultDescription
kind'box'—Discriminator.
idstring—Stable id, unique within the slot. Sibling elements anchor to it with anchor.to: '#id'. The sandbox assigns one on creation.
style.backgroundColorColorValuetransparentFill colour. Set to transparent for an outline-only box.
style.borderColorColorValuetransparentStroke colour.
style.borderWidthDimension0 ptStroke 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.borderRadiusDimension0 ptCorner radius. Clamped to half the smaller side at render time.
placementElementPlacement—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).
reservebooleantrueHeading designs only: whether the box counts toward the height the heading reserves in the text flow (see 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.

{
  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.

PropertyTypeDefaultDescription
idstring—Stable identifier; other elements may anchor to it as #id.
resourceIdstring—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.
decorativebooleanfalseThe picture is decoration only (an ornament, a band): it gives no alternative text to the output even when its resource has one (see below).
placementElementPlacement—Anchor, offset and size (see Element placement). A 'fill' side runs to the container edge.
parity, pagesas above'all'Which pages the image appears on.
reservebooleantrueHeading designs only: whether the image counts toward the height the heading reserves in the text flow (see 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:

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:

{ "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).

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). 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.

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.