# 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

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

## In short

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](https://postext.dev/en/docs/configuration-composed-pages.md). A document without composed pages lays out exactly as before.

## The page fence

```md
::::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; the template must be a spread template too. |
| `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](https://postext.dev/en/docs/comics.md)) 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 `::::page` line 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.

## Frames and slots

```md
:::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 `takes` its level (`takes: "h1"`), if the template has one, so the title of a textbook topic is just `# Title` at 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.

## Figures with labels

```md
:::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.

- `at` names 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.
- `style` names 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.

## Example

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:

```md
::::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.

## How the page is set

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.

### Placing the frames

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.

### The flow frame and headings

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.

### Chaining frames

`next` sends what a frame cannot hold to another frame of the same page, and that frame to another, like threaded text frames:

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

### Frames that push text

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.

### Decoration, running heads and layers

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.

### Content that does not fit

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`](https://postext.dev/en/docs/composed-pages.md#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.

## Warnings

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](https://postext.dev/en/docs/configuration-programmatic-usage.md#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 `::::`. |
| `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. |

### frameOverflow

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.

## From code

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