Repository navigation
Composed Pages
The Markdown of composed pages: the four-colon page fence that names a page template, frames and the slot attribute that send content to the frames of the template, figures with labels at the anchors of a picture, and the warnings that check the content against the template
Generated from docs/composed-pages-en.mdx (updated 2026-10-16). Read it on postext.dev, where it is also available in Spanish, Catalan, Portuguese, French, German, Italian, Chinese, Japanese and Arabic. To change this page, edit the MDX and run pnpm wiki in apps/web.
Some pages are not one column of running text but a set of boxes, like a page of a school textbook with a title band, a text and three coloured boxes with photos. Postext calls them composed pages. The shape of the page is a template in the configuration, and the Markdown only says which piece of text goes into which box. This page shows how to write that Markdown: the page fence, the frames, the slot names, and pictures with labels such as the names on a map. It also lists the warnings Postext gives when the text and the template do not match.
The template holds the form of the page; the Markdown holds its content and says which frame each piece goes to.
A composed page is a page laid out from a template: named frames (a title band, a text, a box with its photo), groups that move together and a decoration, all defined in the configuration under pageTemplates. The Markdown of the page is a fenced block that names its template and pours content into the frames by name. No position or size is ever written in the Markdown, and no content text is ever written in the configuration, so a translation is the same template with other words.
This page covers the Markdown and how the page is set from it. The template itself (its frames, sizes, styles and decoration) is described in Configuration › Composed pages. A document without composed pages lays out exactly as before.
::::page{template="tema-cajas" fondo="banda-retos"}
# La actividad económica y el dinero
Las personas utilizan :chip[recursos]{style="clave"} que se obtienen de la naturaleza…
::::A composed page opens with a line of four colons followed by page and its attributes, and closes with a bare line of the same number of colons. Inside, three-colon containers (:::frame, :::callout, :::figure, :::columns, :::paper) open and close as they always do, so a bare ::: closes the innermost of them and never the page. A page written with five colons (:::::page) closes with five; a four-colon line inside it is then ordinary text.
| Attribute | Effect |
|---|---|
template="…" |
Required. The id of an entry of pageTemplates. |
spread |
The page is a spread of two facing pages (see Spreads). It must agree with the template's spread; otherwise the template decides and spreadMismatch says so. |
id="…", #id
|
An anchor: a :ref to it prints the page the composed page lands on. |
| any other | A value the template reads as {attr.name}, such as a background picture chosen page by page: fondo="banda-retos" with resourceId: "{attr.fondo}" in the decoration. |
A composed page always starts a new page (or spread), and the text after its closing fence starts on the next one.
The three-colon :::page{split=…} is a comic page (see Comics) and keeps that meaning. A three-colon :::page that carries template= is still read as a comic page, and the warning pageFenceLength says that a composed page needs four colons.
When a fence is missing, the parser recovers without losing text:
- a page left open runs to the end of the chapter and is reported as an unclosed container at its opening line;
- a
::::pageline met while a page is open closes the open page there first (reported the same way); - the closing fence closes any frame or box still open inside the page, reporting each one;
- a closing fence with another number of colons is not the page's: it stays in the text, where you can see it.
:::frame{slot="dinero" label="El dinero"}
Es el instrumento utilizado como medio de pago…
Hasta hace pocos años, lo más normal era pagar con dinero en **efectivo**…
:::A slot is the name a piece of content asks for; it matches the name of a frame of the template. :::frame{slot="…"} sends everything inside it to that frame. Its body is full Markdown: paragraphs, lists, headings, chips, ::resource embeds, :ref references, footnotes, :::columns and nested callouts. label and title give the frame's tab or title its words, as they do for a :::callout; the frame's look comes from the callout style the template gives it.
A single block can name its slot itself:
| Block | Written |
|---|---|
| A heading | ## Retos {slot="retos-titulo"} |
| A resource embed |
::resource{id="dinero-pago" slot="dinero-fotos"} (the two attributes, in any order) |
| A code listing | `````js {slot="codigo"}`` |
| A box or any other container |
:::callout{type="nota" slot="aside"}: the whole box goes to the slot |
| A figure |
:::figure{resource="mapa" slot="mapa"} (see below) |
How the content is shared out:
- Several pieces with the same slot are joined in Markdown order.
- Content with no slot goes to the template's flow frame (the frame marked
flow). - A heading with no slot goes first to the frame that
takesits level (takes: "h1"), if the template has one, so the title of a textbook topic is just# Titleat the top of the page. - Content that names a slot the template does not have goes to the flow frame and is reported (
slotUnknown).
A slot outside a composed page is read and changes nothing.
:::figure{resource="mapa-europa" slot="mapa"}
::label[OCÉANO ATLÁNTICO]{at="atlantico" style="mar"}
::label[**Madrid**]{at="madrid"}
::label[Mar Negro]{at="62% 48%"}
::::::figure{resource="…"} places one resource, with its caption and number, as ::resource does, and lists the labels to set on the picture: one ::label[TEXT]{…} line each, blank lines allowed between them.
-
atnames an anchor of the resource (a point defined once on the picture, shared by every language), or gives a point in percentages of the picture,at="x% y%", for a label with no anchor. -
stylenames a label style and overrides the anchor's. - The text in brackets is inline Markdown: bold, italic, chips.
A figure works in the running text as well as in a frame. Labels are read and kept with their source; drawing them on the picture comes in a later release, and until then a figure is set as its resource is. A line inside a figure that is neither a label nor blank ends a figure whose closing ::: is missing, and is reported as an unclosed container.
The page of a school textbook, with a title band, a text with its list and three coloured boxes, each with its photo beside it:
::::page{template="tema-cajas" fondo="banda-retos"}
# La actividad económica y el dinero
## La actividad económica
Las personas utilizan :chip[recursos]{style="clave"} que se obtienen de la naturaleza…
- Las :chip[empresas.]{style="clave"} Ofrecen bienes y servicios a cambio de un beneficio.
- Las :chip[personas y familias.]{style="clave"} Adquieren lo que ofrecen las empresas…
:::frame{slot="dinero" label="El dinero"}
Es el instrumento utilizado como medio de pago, ya que su valor es conocido…
:::
::resource{id="dinero-pago" slot="dinero-fotos"}
:::frame{slot="gasto" label="El gasto"}
- **Fijo:** es el que se produce todos los meses…
- **Variable:** puede cambiar según las necesidades…
:::
::resource{id="gasto-compras" slot="gasto-fotos"}
:::frame{slot="ahorro" label="Préstamos y ahorro"}
Si necesitas o quieres comprar algo muy caro…
:::
::resource{id="ahorro-balanza" slot="ahorro-fotos"}
::::The title goes to the frame that takes h1; the subheading, the paragraph and the list go to the flow frame; each box and each photo go to the frame of their slot. The Catalan edition is the same lines with other words: the template, the slots and the photos do not change.
A composed page takes a page of its own (role composed for the pages filter of design elements). The template is chosen for the side the page falls on, so its variants.recto or variants.verso applies. Then each frame is placed and filled, one after another, with the typography of the rest of the book: paragraph and heading styles, lists, chips, footnote markers and the callout style of the frame.
A frame stands in its grid cell, or where its placement puts it (an anchor, an edge and an offset, as for a design element), or, with neither, at the top-left corner of the template's area, as wide as the area. A frame anchored to another one ("to": "#dinero") is placed after it and moves with it: below hangs it under the other frame, right-of beside it, and a corner such as top-right pins it to that corner of the other frame's box.
Its height is fixed (a length or a percentage), fill (down to the edge of the area), or auto, the default: the height of the content it receives, between minHeight and maxHeight, and never past the foot of the area. A frame taller than its content places it as verticalAlign says (top, middle, bottom, or justify to spread the space between its blocks). The frame's callout style draws its background, border, rounded corners, stripe and icon at the frame's full size; the label of the :::frame fence prints in the style's tab.
Content that names no slot goes to the frame marked flow; the first heading of a level goes to the frame that takes it. The frames of a page are read in the order their content first appears in the Markdown, each one whole: a screen reader, the tagged PDF and the reflowable outputs read a box after the text that opens the page, not in the middle of it.
next sends what a frame cannot hold to another frame of the same page, and that frame to another, like threaded text frames:
{ "name": "columna-1", "flow": true, "next": "columna-2", "height": { "value": 120, "unit": "mm" } },
{ "name": "columna-2", "placement": { "anchor": { "to": "#columna-1", "edge": "right-of" } } }The text is cut between lines and goes on in the next frame, broken again at that frame's width, with nothing lost or repeated. overflow.continueIn: "#frame" means the same. Continuing on the next page is planned ("next-page").
A frame with exclude: "push" keeps the text of the other frames out of its rectangle: a line of a frame that meets it is set short on the side the rectangle covers, as text wraps round a picture in the running text. A photo anchored to the corner of a box and marked push makes the box's text run beside it. A box that should keep a fixed column free for its pictures can use padding instead.
The template's decoration (pictures, rules, boxes and short texts) is laid once the frames have their final boxes, so an element can anchor to a frame ("to": "#gasto") and follow it. Its texts read the page's own attributes: fondo="banda-retos" on the page fence gives {attr.fondo}, for example a background picture chosen page by page with "resourceId": "{attr.fondo}". The running heads read them too. A template's header or footer replaces the section's, false hides it, and with neither the section's running heads print.
Frames paint in the order of their layer: the decoration at layer 0, frames at 1 by default, a higher layer over a lower one, a negative layer under the decoration. rotate turns a frame and its content together by any angle, about the frame's centre, in the canvas, the PDF, the HTML viewer and the fixed-layout EPUB.
A frame whose content is taller than the frame, with no next frame to go on in, keeps all of it in the document but shows only what fits: the rest is cut at the frame's foot on paged output, and the warning frameOverflow says how much. Content that no frame takes (no flow frame, or a frame missing on this side) is set in the running text on the page after the composed page.
Every output reads a composed page in the order of its Markdown, not in the order the template lists its frames or paints its layers.
-
Reflowable EPUB. The page becomes a section (
<section class="pt-composed pt-composed-tema-cajas">) that opens with the page's page-break marker. Each frame is one block of it (<div class="pt-frame pt-frame-dinero">), placed where its content first appears in the Markdown; a frame and the frames itsnextchain goes on in make one block. A frame drawn in a callout style keeps that style's box: background, border, padding, body text, and itslabelas the box's title. A frame with no style is a plain block. The decoration (background pictures, cards, design text) belongs to the printed page and is left out. -
Figure labels. The picture an EPUB carries does not show the
::labeltexts of a:::figure, so the reflowable book lists them right after the picture, inside its figure, in the order written: a list hidden from sight (pt-figure-labels pt-hidden) that a screen reader reads once. In the HTML viewer, the fixed-layout EPUB and the tagged PDF they follow the picture's alternative text,A family with a shopping basket (Rent, Electricity, Food); Chinese and Japanese labels take(…、…), Arabic ones the Arabic comma. -
HTML viewer and fixed-layout EPUB. Both show the printed page with its template applied: each frame in its box, turned by
rotate, clipped when its content overflows, painted over the frames of lower layers, the decoration at layer 0. The markup lists the frames in reading order and paints them in layer order withz-index, so assistive technology follows the Markdown while the page looks as printed. Decorative pictures are hidden from assistive technology (alt=""). The decoration's faces are among those the Sandbox embeds in an EPUB; a fixed-layout EPUB written without one of them reportsmissingFont. -
Tagged PDF. The structure tree reads the frames in the same order. Each frame, or chain of frames, is a
Divholding its headings, paragraphs and lists; its pictures areFigureelements whoseAltcarries the labels; the decoration is a layout artifact.
From code, a figure's labels are on its block as resourceBlock.labels (text, anchor or point, style and source range), and composedReadingOrder(composedPaintOrder(page)) lists a page's decoration and frames in reading order.
The checks read the Markdown and the configuration before any layout, so they show in the Checks panel as you type, in postext check and in the document's contentWarnings, each with the source range of what it concerns (see Configuration › Warnings in the document).
| Kind | Cause | Fix |
|---|---|---|
templateUnknown |
The page fence names a template that no entry of pageTemplates has, or names none. Its slots are not checked. |
Correct the id, or add the template to the configuration. |
slotUnknown |
A slot="…" names no frame of the page's template (frames inherited through extends and frames of a variants page count). The content goes to the flow frame. |
Correct the slot name, or add a frame of that name to the template. |
slotEmpty |
A frame of the template receives nothing on this page: no slot names it, it takes no heading, and, for the flow frame, no content is left without a slot. A frame that only receives what another frame continues into it (next) is not reported. postext check lists it as a note. |
Write content for it with its slot, or mark the frame optional: true in the template. |
pageFenceLength |
A three-colon :::page carries template=. It is read as a comic page, as before. |
Open the page with four colons, ::::page{template="…"}, and close it with ::::. |
spreadMismatch |
The fence's spread and the template's disagree. The page is laid as the template says (see spreadMismatch). |
Write spread on the fence of a spread template, or remove it for a single-page template. |
unclosedContainer |
A composed page, a frame or a figure is never closed (see the recovery rules above). The Checks panel names the composed page ::::page. |
Add the closing fence: as many colons as the page's opening line, or ::: for a frame or a figure. |
This warning comes from the layout, not from the source checks above: it appears once the page has been set, in the Checks panel, in postext check and in the document's contentWarnings.
Cause. The content poured into a frame (through its slot, or as the flow frame's content) is taller than the frame, and the frame has no next frame left to continue in. Most often there is more text than the frame holds: a translation that runs longer than the original, or a frame held to a fixed height or maxHeight. What does not fit is cut at the foot of the frame in paged output; it stays in the document, in the source map and in the reflowable EPUB. The warning gives the template, the frame, the page, the height cut (overflowMm) and configPath, the JSON pointer of the frame in the configuration (/pageTemplates/0/frames/2).
Fix. Give the frame more room (raise or remove its height or maxHeight), chain it to another frame with next, shorten the text, or set the page with another template. The layout does not yet shrink the text to make it fit.
A composed page rearranges itself inside the limits its template declares. Write a longer text in the first box of a page whose boxes form a stack group, and the box grows to hold it while the boxes below, and the photos and decoration anchored to them, move down; no line of the template changes. A shorter text gives the box back its declared height. A frame or a decoration element marked pin stays where the template puts it, for a box that has to sit on the artwork. How stacks and rows share space, and which frame gives way first, is set in the template: see Configuration › How groups share space.
When the boxes reach the foot of the area and no frame of the group can give more space, the last frame is cut at the foot and the layout reports frameOverflow (see Warnings): shorten the text, lower another frame's minHeight, let a frame shrink, or give the group more room. Groups add no other warning.
Each composed page keeps a log of what the layout changed against the template's own drawing: which frame grew, gave space back or moved, by how much, and which frame set it off. A page whose text fits its template logs nothing.
dinero grew 13.9 mm; gasto moved down 13.9 mm; gasto-fotos moved down 13.9 mm; ahorro moved down 13.9 mm
| Event | What it says |
|---|---|
grew |
The frame is taller (or, with axis: 'x', wider) than declared: fromMm is the declared size, toMm the size it got. |
resized |
The frame is smaller than declared: it gave space to cause. |
moved |
The frame's top edge (with axis: 'x', its left edge) moved, in mm from the sheet's edge; cause is the frame whose growth moved it. |
overflow |
Content did not fit: toMm is the height cut, as in frameOverflow. |
Changes under half a millimetre are left out, and each change is rounded once to a tenth of a millimetre, so frames that moved together read the same. Decoration that follows a frame is not listed.
postext check prints one reflow line per composed page that has events, after the warnings; with --json, the report holds a reflow array with one entry per page: pageIndex, pageLabel, template, fenceIndex, chapter, events and lines (the events in words).
From code, the log is VDTPage.composed.log, and each frame keeps the box its template declares in VDTFrame.declaredRect when it differs from rect. composedReflowLog(doc) returns the entries postext check prints, and describeReflowEvent(event) puts one event in words.
A template with spread: true is one sheet of two facing pages: a band running across the spine, a picture strip from one outer edge to the other, cards on either side. The page fence says so with spread:
::::page{template="linea-tiempo" spread}
# How we measure time
To know when to eat, work or rest, people have always needed to measure time…
:::frame{slot="banda-2"}
With machine clocks
:::
::resource{id="tira-tiempo" slot="tira"}
::::- Where it opens. A spread starts on a verso, the left page of a left-to-right book and the right page of a right-to-left one, and takes the recto after it. When the text before it ends on a verso, the page after that is left blank (a parity blank, as before a comic spread), so the two halves face each other. The text after the closing fence starts on the page after the recto.
-
One sheet. The frames are placed on a sheet as wide as both pages. The
contentarea runs from the outer margin of one page to the outer margin of the other, with both inner margins inside it;trimis the two trimmed pages side by side;bleedadds the bleed at the head, the foot and both outer edges, never at the spine. Agriddivides the whole width: six columns put three on each page. In a right-to-left book the sheet is mirrored as its pages are, so the first column stands on the right page. -
The gutter.
gutteris a band of that width centred on the spine. A frame withoutcrossGutter: trueis kept off it: a frame reaching across the whole band is cut at the band's edge on the side of its centre, and one whose inner edge falls inside the band moves out of it whole. Anautoheight is measured at the narrower width, and the page's reflow log records the change (resizedormoved, with causegutter). Without agutterthe band is the spine itself, so only frames that ask for it cross the fold. -
Across the spine. A frame with
crossGutter: truemay run across. Its text is read once, on the page that holds the frame's centre; the facing page paints its own half of the frame, cut at its sheet, so a band or a picture reads as one piece across the open book. A tagged PDF marks that half as an artifact, the HTML hides it from assistive technology, and selecting or searching the text finds it once. -
Decoration is laid once over the whole sheet and cut at the spine the same way: a ground, a card from one page to the other, a running head on the right page. An element anchored to
#framefollows that frame onto the page it lands on. A spread takes novariants, since it has both sides;headerandfooterwork on each of its pages as on any page.
On the canvas, in the Sandbox's spread view and in the Folio the two pages stand side by side. The PDF has two pages, each clipped at its bleed. The fixed-layout EPUB gives them page-spread-left and page-spread-right with rendition:spread-both, so a reader shows them together in either orientation. The reflowable EPUB reads the frames as it reads any composed page, in Markdown order.
Cause. The page fence and its template disagree: the fence has spread and the template is a single page, or the template is a spread (by its own spread: true or through extends) and the fence lacks the flag. The template decides: a spread template is laid over two facing pages, any other on one page. The warning gives the template, the fence's flag (fence) and the template's (templateSpread), and points at the spread attribute, or at the template's name when the fence has none.
Fix. Make the fence say what the template is: write spread on the fence of a spread template, remove it from the fence of a single-page one, or choose another template.
composedPages(blocks) reads the parsed blocks of a chapter (parseMarkdownWithIssues(markdown).blocks) and returns one node per page fence, in order: its template, spread, attrs with the source range of each value (attrSources), its fenceIndex (the ordinal of the fence in the chapter, the same in every language), slots (the indices of the blocks of each slot, in Markdown order), unassigned (the blocks with no slot) and the source range of the whole page. A block carries the slot it names in ContentBlock.slot, with the source range of the value; a figure is a resourceBlock whose figure holds its attributes and labels, each label with its text, its inline spans and a source map back to the Markdown. checkComposedSlots(pages, config) returns the three slot warnings for those pages.
After layout, a composed page's VDTPage.composed records its template, the variant applied, the page fence's attributes and, for each frame, its id (cp0:dinero: the page fence's ordinal and the frame's name, the same in every language), its box, layer, turn, the height of content that did not fit (overflowPx), the frame it continues from or into, its config path (/pageTemplates/0/frames/2) and the ids of its blocks. Those blocks are ordinary callout blocks in page.floats and doc.blocks, with their source ranges, so a click in an upright frame of the Sandbox preview lands on its Markdown. composedPaintOrder(page) gives the order a renderer paints the frames and the decoration in, and frameMatrix(frame) the turn of a frame.
Postext is MIT-licensed. Maintained by Ignacio Ferro and community contributors. See the live documentation for the most up-to-date reference.
Overview
Writing
- Document Format
- Hyphenation and Justification
- Chinese Layout
- Arabic Layout
- Japanese Layout
- Comics
- Composed Pages
- Sandbox
- Agent Skill
- Command Line
- WebMCP
Configuration
- Overview and index
- Page and layout
- Headers and footers
- Body text
- Hyphenation and language
- Headings
- Widows, orphans and balancing
- Lists and maths
- Notes and references
- East Asian typography
- Resources
- Styles and parts
- Callout boxes
- Comics
- Composed pages
- Fonts, colours, viewers
Code
Internals
External