Skip to main content

Chapter 3.14 · Part II · The craft

Configuration: composed pages

Page templates for composed pages: frames, groups, decoration, inheritance and recto/verso variants, the label styles of picture anchors, and the warnings a template can give

Updated 2026-10-166 minIn chapter 3 Configurationenescadefritptzhjaar

In short

This page covers page templates. A template is a page drawn in the settings: named boxes called frames, where each one sits, how big it may grow and how it looks. The Markdown of a composed page then fills the frames by name, so the text never holds positions and the settings never hold text. A template can start from another one and change only a few frames, and it can differ on left and right pages. The page also lists the label styles for words set on pictures, and every warning a template can give with the way to fix it.

#Page templates

A composed page is a page whose form comes from a template in the configuration and whose content comes from the Markdown of a ::::page{template="…"} fence. The template names rectangles, called frames; the Markdown sends each piece of content to a frame by its name (its slot). Geometry never appears in the Markdown, and content text never appears in the template. The pageTemplates property holds the templates of the book:

const config: PostextConfig = {
  calloutStyles: [{ id: 'caja-azul' }, { id: 'tab-titulo' }],
  pageTemplates: [
    {
      id: 'tema-cajas',
      decoration: {
        elements: [
          { kind: 'image', id: 'fondo', resourceId: '{attr.fondo}', decorative: true,
            placement: { anchor: { to: 'bleed', edge: 'top-left' }, size: { width: 'fill', height: 'fill' } } },
        ],
      },
      frames: [
        { name: 'titulo', takes: 'h1', style: 'tab-titulo' },
        { name: 'texto', flow: true },
        { name: 'dinero', style: 'caja-azul', padding: { right: { value: 62, unit: 'mm' } } },
        { name: 'dinero-fotos', layer: 2, width: { value: 60, unit: 'mm' },
          placement: { anchor: { to: '#dinero', edge: 'top-right' } } },
      ],
      groups: [
        { name: 'pagina', kind: 'stack', gap: { value: 6, unit: 'mm' }, frames: ['titulo', 'texto', 'dinero'] },
      ],
    },
  ],
};

A template that sets nothing but its id and one frame is a whole page with that frame filling the content area. Every other field is optional:

PropertyTypeDefaultDescription
idstringrequiredThe name a page fence asks for (template="tema-cajas"). Unique among the templates: a second one with the same id is ignored.
namestringthe idThe name shown in the Sandbox. Layout never reads it.
extendsstringnoneThe id of a template this one starts from. See Inheritance and variants.
area'content' | 'trim' | 'bleed''content'The rectangle frame positions refer to. 'content' is the page's content area, mirrored on left and right pages like the running text; 'trim' is the trimmed page; 'bleed' the page with its bleed.
grid{ columns, rows, columnGap?, rowGap? }noneDivides the area into cells, so a frame or a group can be placed by cell (grid: { col: 2, row: 1, cols: 2 }, 1-based) instead of by placement. The gaps default to 0.
framesFrameConfig[][]The frames, in template order: the order measured first and the one that breaks ties. See Frames.
groupsFrameGroupConfig[][]Stacks and rows of frames that share space. See Groups.
decorationDesignSlotnoneImages, rules and boxes painted with the page, in the element language of headers and footers. Text elements hold placeholders only ({pageNumber}, {chapterTitle}, {attr.x} for an attribute of the page fence). An element may anchor to a frame or a group ('#dinero') and then moves with it. Decoration is laid after the frames and never pushes text.
header, footerDesignSlot | falseinheritedThe running head and the folio of the page. Unset, the page keeps those of its chapter; false hides them.
spreadbooleanfalseThe template describes two facing pages laid as one sheet. A spread always starts on a left page in a left-to-right book (a right page in a right-to-left one), with a blank page added before it when needed.
gutterDimension0On a spread, the band at the spine that frames keep clear of, unless they set crossGutter: true.
variants{ recto?, verso? }noneChanges for a right page (recto) or a left page (verso), written like the template itself. The side is the physical one, so left-to-right and right-to-left books both work.
overflowTemplatestringthis templateThe template of the pages added when a frame continues on the next page (overflow.continueIn: 'next-page'). Unset, the added page uses this template with only the continued frames filled.
bodyStylestringnoneA paragraph style for the text of the page, as heading styles have.
palettestringnoneA palette override for the page, as heading styles and parts have.
rolestring'composed'The page role that design elements with a pages filter see.

#Frames

A frame is a named rectangle that receives typeset content. Its name is the slot the Markdown asks for, and it is unique in its template (a group cannot take the name of a frame either, since anchors name both the same way).

A frame is placed by its grid cell when the template has a grid, else by its placement, which works like a design element's: an anchor ('container' is the area, 'page', 'bleed', or '#name' for another frame or group), an edge, an offset and a size. A frame anchored to another one moves with it. Without either, the frame takes the whole area.

PropertyTypeDefaultDescription
namestringrequiredThe slot name.
placementElementPlacementthe whole areaAnchor, edge, offset and size, as in element placement. Ignored when a grid cell places the frame.
grid{ col, row, cols?, rows? }noneA cell of the template grid, 1-based, spanning cols × rows cells (default 1). A cell past the grid is moved back inside it. Ignored when the template has no grid.
width, heightDimension | { percent } | 'auto' | 'fill''fill', 'auto'A fixed length, a percentage of the area (0 to 100), 'auto' (the content's own size) or 'fill' (what is left up to the edge the frame is anchored to). Unset, the frame reads placement.size, then the default: full width, as tall as its content. A text frame needs a measure, so 'auto' width only makes sense for a frame that holds one picture (the picture's width at the frame's height). A value the engine cannot use (a negative length, an unknown unit) reads as the default.
minWidth, maxWidth, minHeight, maxHeightDimensionnoneLimits for an 'auto' or 'fill' size and for the space a group gives or takes. The minimum wins over the maximum, and a fixed size outside them is brought between them (frameMinMax warns of both).
stylestringframelessA callout style id: frames are drawn with callout styles (background, border, padding, stripe, icon, label tab, title). The words of a tab come from the Markdown (:::frame{slot="dinero" label="El dinero"}). The callout settings about where a box goes in the running text (placement, span, columns, width, fixed…) do not apply to a frame.
padding{ top?, right?, bottom?, left? }the style'sOverrides the style's padding side by side: room for the photos that sit over a box, for example.
layernumber1Paint order. Decoration is layer 0; a negative layer paints below it.
rotatenumber0Degrees, clockwise. The box and its content turn together.
verticalAlign'top' | 'middle' | 'bottom' | 'justify''top'Where the content sits in a frame taller than it.
columns, columnGapnumber, Dimension1, the style'sColumns inside the frame.
exclude'none' | 'push''none''push': the text of other frames wraps around this one's rectangle.
flowbooleanfalseThe frame receives the content that names no slot.
takes'h1' … 'h6'noneThe frame receives the first heading of that level that names no slot, so a page title is just # Title at the top of the fence.
nextstringnoneA chain: content that does not fit continues in the frame of that name, on the same page.
overflow.continueInstringnoneWhere content that does not fit goes on: '#frame' (another frame of the page, as next), 'next-page' (the same frame on a page added after this one) or 'template:<id>' (a page of that template).
overflow.markstring | falsethe language'sThe mark at the end of a cut piece. Unset, the continuation word of the document language (as split tables use); false sets none.
fit.shrinkTextbooleantrueWhen nothing else makes the content fit, its text may be set smaller.
fit.minScalenumber0.9The smallest text scale, above 0 and at most 1 (a larger value reads as 1).
fit.scaleImages, fit.imageMinScaleboolean, numbertrue, 0.8Whether the pictures in the frame may be set smaller, and down to what scale.
pinbooleanfalseThe frame never moves or resizes, for a box that must stay aligned with the artwork. A group flows around it.
grow, shrink, prioritynumber0How the frame shares the space of its group: grow takes a share of the spare space, shrink lets the frame give space back, and frames with a lower priority give it first.
optionalbooleanfalseNo warning when the Markdown sends nothing to the frame.
crossGutterbooleanfalseOn a spread, the frame may cross the spine band.
writingMode, direction'horizontal-tb' | 'vertical-rl', 'ltr' | 'rtl'the page'sA vertical box on a horizontal page, or the other way round.
removebooleanfalseIn a template that extends another, or in a variant: drop the inherited frame of that name.

#Groups

A group is a stack (top to bottom) or a row (start to end) of frames that share space and move together. Inside a group, the frames' own position along the group's direction is ignored: the group lays them out in order, with gap between them. When the text of one frame grows, the frames after it move along. A group is a box that frames and decoration can anchor to ('#pagina').

PropertyTypeDefaultDescription
namestringrequiredUnique among the frames and groups of the template.
kind'stack' | 'row''stack'Top to bottom, or start to end.
framesstring[][]The frames of the group, in their order on the page. A frame belongs to one group at most; a name that is no frame of the template, or a frame already listed, is skipped.
gapDimension0The room between two frames of the group.
placement, gridas framesits frames' boxThe group's own box. Unset, the box around the declared boxes of its frames.
align'start' | 'center' | 'end' | 'space-between''start'Where the spare space goes when no frame of the group grows.
removebooleanfalseIn a template that extends another, or in a variant: drop the inherited group.

#Inheritance and variants

A template with extends starts from the template it names (which may itself extend another) and changes only what it writes:

  • A field such as area, header or overflowTemplate replaces the parent's. grid merges key by key. spread, gutter and role are inherited too; id, name and extends are the template's own.
  • frames and groups merge by name: an entry with the name of an inherited one changes the fields it writes and keeps the rest (fit, overflow and padding merge key by key; placement is replaced whole); remove: true drops it; a new name is added after the inherited ones.
  • Decoration elements merge by id the same way; an element with a new id, or none, is added.

variants.recto and variants.verso are written like a template and merge the same way, on a right or a left page. They apply after the whole extends chain, the root's variant first. A spread has both sides, so no variant applies to it.

pageTemplates: [
  { id: 'tema', frames: [{ name: 'texto', flow: true }, { name: 'margen', width: { value: 30, unit: 'mm' },
      placement: { anchor: { to: 'container', edge: 'top-left' } } }],
    variants: { recto: { frames: [{ name: 'margen', placement: { anchor: { to: 'container', edge: 'top-right' } } }] } } },
  { id: 'tema-sin-margen', extends: 'tema', frames: [{ name: 'margen', remove: true }] },
]

A value equal to a default is never dropped when the Sandbox saves a template: in a template that extends another, or in a variant, it overrides the parent's value.

From code, resolvePageTemplate(config, id, { parity }) returns a template with its chain merged, the variant of the side applied, every default filled in and every reference settled as the warnings below describe; validatePageTemplates(config) returns the warnings.

#Label styles

The labelStyles property names the looks of the labels set on a picture: the seas and towns of a map, the parts of a diagram. A label stands at an anchor of the picture (a point stored with the resource, as comic speakers are) and takes the style the anchor or the label names.

labelStyles: [
  { id: 'mar', fontStyle: 'italic', textTransform: 'uppercase', letterSpacing: 0.12,
    color: { hex: '#2b6ca3', model: 'hex' } },
  { id: 'ciudad', fontWeight: 600, halo: { color: { hex: '#ffffff', model: 'hex' }, width: { value: 0.6, unit: 'pt' } } },
]
PropertyTypeDefaultDescription
idstringrequiredThe name anchors and labels use.
namestringthe idThe name shown in the Sandbox.
paragraphStylestringnoneA paragraph style whose type the label borrows; the fields below override it.
fontFamily, fontSize, fontWeightstring, Dimension, numberthe body text'sThe type of the label.
fontStyle'normal' | 'italic''normal'Upright or italic.
colorColorValuethe body text'sThe colour of the letters.
textTransform'none' | 'uppercase''none'Capitals, as maps set seas and countries.
letterSpacingnumber0Extra space between letters, in ems.
halo{ color, width }noneAn outline behind the letters, so they read over a busy picture.
background, paddingColorValue, DimensionnoneA plate behind the label, and the room around its text.
minScalenumber0.85How small a label may be set when it does not fit its width.

#Label fields of an anchor

A picture's anchors (Resource.anchors, the points comics use for speakers) take a few optional fields for labels. They are fractions of the picture, so they hold in every language and through any crop of the picture. Comics ignore them.

FieldTypeDefaultDescription
widthnumberthe label's ownThe measure of the label, as a fraction of the picture's width: a long name breaks onto two lines inside it.
align'start' | 'center' | 'end''center'Which side of the label stands at the anchor point.
verticalAlign'top' | 'middle' | 'bottom''middle'Which edge of the label stands at the anchor's height.
rotatenumber0Degrees, clockwise, around the anchor point: a river's name along its course.
stylestringnoneA labelStyles id. A label that names its own style overrides it.

#Page template warnings

Elsewhere the engine ignores a key it does not know. Page templates and label styles are checked instead, and every problem is a configuration warning in postext check and in the Sandbox's Checks panel. The path of these warnings is a JSON pointer to the field at fault: /pageTemplates/3/frames/2/style, /pageTemplates/0/variants/recto/frames/1/next, /resources/4/anchors/0/style. When the field was inherited, the pointer names the template that wrote it. A warning that names an id or a name that does not exist also suggests the closest one that does.

A misspelt key of a template, frame, group, grid, fit, overflow, variant or label style is an unknownConfigKey; a word outside a setting's choices (area, verticalAlign, exclude, takes, kind, align, writingMode, direction, fontStyle, textTransform) is an unknownConfigValue, and the setting keeps its default. A template with no id, or a frame or group with no name, is ignored with an unknownConfigValue at that field.

WarningCauseFix
templateDuplicateTwo templates have the same id. The first one is used and the later one is ignored.Rename one of them, or delete the copy.
templateExtendsCycleFollowing extends comes back to the same template (a → b → a). Every template of the loop, and every template that extends one of them, is dropped: no page can use it.Remove the extends of one template of the loop.
templateExtendsUnknownextends names an id no template has. The template is used on its own, with nothing inherited.Correct the id (the warning suggests the closest one), or add the parent template.
frameDuplicateTwo frames, or two groups, of one list have the same name, or a group has the name of a frame. The later entry is ignored; an anchor to that name reads the frame.Give each frame and group a name of its own. To change an inherited frame, write one entry with its name.
frameStyleUnknownA frame's style names no callout style. The frame is drawn without a box.Correct the id, or add the style to calloutStyles.
frameAnchorUnknownA frame, a group or a decoration element is anchored to '#name' and the template has no frame, group or element of that name; or frames and groups are anchored to each other in a loop (also through a group anchored to nothing of its own, whose box is that of its frames). The anchor reads as 'container', the area.Anchor to a frame or group that exists, or break the loop by anchoring one of them to the area.
frameMinMaxA minimum is larger than its maximum (minHeight 40mm > maxHeight 30mm), or a fixed width or height lies outside them. The minimum wins, a fixed size is brought between the limits, and used says what the frame takes. Lengths in different units are compared at the page's resolution, ems at the body text size.Make the minimum no larger than the maximum, or drop the limit the fixed size contradicts.
groupFrameUnknownA group's frames lists a name that is no frame of the template (perhaps removed by a child template or a variant). The group skips it.Correct the name, or remove it from the list.
groupFrameTwiceA frame is listed in two groups, or twice in one. A frame belongs to one group only: the later listing is skipped.Keep the frame in the one group it moves with.
frameChainLoopFrames continue into each other in a loop through next or overflow.continueIn: '#frame' (a → b → a). The link that closes the loop is dropped, so the chain ends at the frame before it.End the chain at its last frame: remove its next, or make it continue on the next page.
overflowTargetUnknownA next names no frame of the template; an overflow.continueIn names no frame ('#x') or no template ('template:x'), or is in none of the forms '#frame', 'next-page' and 'template:<id>'; or overflowTemplate names no template. Content that does not fit does not continue there.Correct the name or the id, or write one of the three forms.
labelStyleUnknownAn anchor of a picture names a style that labelStyles does not have. The warning names the resource. The labels at that anchor take the default look.Correct the style id on the anchor, or add the style to labelStyles.

The configuration version is 15 since page templates (bundles written before keep loading unchanged: nothing an older configuration says changes meaning).