# Sandbox

> The Sandbox's interface: the activity bar and the Text, Books, Chapters, Resources and Fonts panels, and the layout on phones

- HTML version: https://postext.dev/en/docs/sandbox
- Last updated: 2026-11-04
- Reading time: 28 min
- Other languages: [es](https://postext.dev/es/docs/sandbox.md), [ca](https://postext.dev/ca/docs/sandbox.md), [de](https://postext.dev/de/docs/sandbox.md), [fr](https://postext.dev/fr/docs/sandbox.md), [it](https://postext.dev/it/docs/sandbox.md), [pt](https://postext.dev/pt/docs/sandbox.md), [zh](https://postext.dev/zh/docs/sandbox.md), [ja](https://postext.dev/ja/docs/sandbox.md), [ar](https://postext.dev/ar/docs/sandbox.md)

## In short

The Sandbox is a free tool on this website for trying Postext without installing anything. You write your text on one side and see the finished pages on the other, and they change as you type. This page explains the parts of the screen and the panels where you write the text, manage books and chapters, add pictures and choose fonts. Other pages cover the design settings, the previews, saving and importing, and putting the Sandbox in your own website.

**An interactive playground for the Postext layout engine.**

The Sandbox gives you an editor-style interface where you write the text, manage resources and fonts, adjust the design, and see the result in real time in five synchronized views: **canvas**, **PDF**, **Folio** (the printed book in 3D), **HTML** and **EPUB 3**. A sixth tab, **SCORM**, packs the chapters and sections you pick for a learning management system. It is built for experimenting. You can change the typography or the number of columns, try different figure placements, and watch how the engine responds. The panels use editorial words (books, figures, boxes, running heads) rather than engine terms, so people from publishing can find their way without knowing the configuration schema. Every control is keyboard- and screen-reader-accessible.

## Interface layout

The Sandbox has three main areas: a narrow **activity bar** for switching panels, a resizable **sidebar** that holds the active panel, and a **viewport** on the right with the output tabs. The panel headers, the viewport's tab bar and the logo cell at the top of the activity bar share one height, so the top band reads as a single rule across the window.

![The Sandbox with the Text panel open on the left showing the Markdown of a book, and the Canvas on the right showing a spread with a map across two pages.](https://postext.dev/docs-shots/en/sandbox-layout.webp)

*The three areas: the activity bar, the Text panel in the sidebar, and the Canvas in the viewport.*

### Activity bar

A narrow strip on the left edge has one button per panel. Each button shows an icon with a one-word label under it, and a longer hint as its tooltip (the same hint is the button's accessible description). From top to bottom:

- **Books** (`Files` icon): your books and the sample books
- **Chapters** (`BookOpen` icon): the open book and its chapters
- **Text** (`FileCode` icon): write and edit the text of the current chapter
- **Resources** (`FolderOpen` icon): images, SVG drawings and tables used in the book
- **Fonts** (`Type` icon): the typefaces the book uses, and your own font files
- **Design** (`Settings2` icon): page, typography, headings, figures, boxes and export settings
- **Checks** (`AlertTriangle` icon): typesetting problems found in the layout. The icon carries a live count badge of current warnings, capped at `99+`.

Clicking the active panel's button collapses the sidebar, and clicking a different button switches to that panel. The bar is a single tab stop: the arrow keys (↑ / ↓, wrapping), **Home** and **End** move focus between the buttons, and Enter or Space opens the focused panel.

At the bottom of the bar, **Large buttons and fields** makes every button and field of the Sandbox at least 44 × 44 px (WCAG 2.5.5). The browser remembers the choice; until you make one, it is on with a touch screen and off with a mouse. The host application can add its own items to the bar: an optional **home logo link** in the top cell (via the `homeUrl` or `homeLink` props), and a **theme toggle** and **language switcher** at the bottom, under that switch. Whole books are exchanged as `.postext` files from the Books panel, and the Text and Design panel headers export their own part (see [Export and import](https://postext.dev/en/docs/sandbox-books-saving.md#export-and-import)).

### Content and Design modes

The bar above the preview starts with a switch between the two ways of working on a book:

- **Content**: the text is edited, in the Text panel and on the pages. The frames of a composed page stay where their template puts them.
- **Design**: page templates and styles are edited. The Text panel shows the chapter read only, with a note and an **Edit the text** button, and its toolbar is off. A click on a frame of a composed page picks that frame for the template editor instead of placing the caret.

**Alt+Shift+M** (**⌥⇧M** on a Mac) switches modes from anywhere in the Sandbox. Switching to Design while the Text panel is open opens the Design panel, and switching back opens the Text panel again. The browser remembers the mode. Agents read and set it with `sandbox_set_view` (`mode`).

![The same spread in Design mode: the Design panel on the left with its groups of settings, and the page with dashed outlines on its frames and a point on each label of the map.](https://postext.dev/docs-shots/en/sandbox-modes.webp)

*Design mode: the sidebar lists the settings, and the page draws its frames and the anchor point of every label.*

### Undo and redo

The Sandbox keeps one history for the text and the design of the open book. **Cmd/Ctrl+Z** takes back the latest change, whichever kind it was: words typed in the Text panel, an edit made on the pages (a comic panel resized, a label moved), a setting changed in the Design panel, a frame dragged or a page template added in Design mode. **Shift+Cmd/Ctrl+Z** (or **Ctrl+Y**) puts it back. The keys work anywhere in the Sandbox except inside a text box, which keeps its own undo for what you type in it. The **Undo** and **Redo** buttons beside the Content / Design switch, and the first two buttons of the Text panel's toolbar, run the same history, and screen readers hear what was taken back.

- A gesture is one step: a slider dragged, a colour picked by dragging or a number typed digit by digit counts once, as long as the pauses are shorter than a second. Each drop of a frame is a step of its own.
- Each chapter keeps its own text history. Undoing a text change of another chapter opens that chapter.
- The history survives the switch between Content and Design: a text edit can be undone in Design mode, and a frame drag in Content mode.
- Opening another book (from the Books panel, a sample, a `.postext` file or a link) starts a new history, and so does resetting the text or the whole book. Resetting only the design is a step, and can be undone.
- Chapters added, removed, split, merged or moved, resources and fonts are not in the history. It lasts for the visit: after a reload it is empty.
- The marks of a picture (its anchors, safe area and lettering zones) are the exception among resources: saving them in the Resources panel, or dragging an anchor on a page, is one step of the history.

Agents undo and redo with `sandbox_undo` (see [WebMCP](https://postext.dev/en/docs/webmcp.md)).

### Sidebar panels

The sidebar is resizable. Drag the separator between the sidebar and the viewport, or focus it with Tab and use the keyboard: ← / → change the width by 2 % (10 % with Shift), and **Home** / **End** jump to the narrowest and widest sizes. The separator announces its current value to screen readers, and the viewport always keeps at least 200 px. The sidebar collapses entirely when no panel is active, and its width is remembered between sessions.

Settings rows put their label beside the control, or above it when the sidebar is narrow. This uses container queries, so it follows the sidebar's width rather than the window's.

#### Text panel

A CodeMirror 6 editor for the chapter's Markdown. Screen readers announce it as *Chapter text*. It has:

- **Syntax highlighting**: headings, emphasis, links, code blocks and blockquotes are visually distinct, with dedicated highlighting for **YAML front matter**, **LaTeX math** regions, **inline chips** (`:chip[…]`: the brackets and attributes are marked and the chip text is tinted), **small capitals** (`:smallcaps[…]`: the brackets are marked and the text is shown in small caps), the **orientation marks** of vertical text (`:tcy[…]`, `:upright[…]`, `:sideways[…]`: the brackets marked, the text with a dotted underline) and the **Chinese annotations** (`:dots[…]`, `:name[…]` and `:book[…]` with their marks drawn under the text; `:ruby[…]{rt="…"}` and `{紅樓|hóng|lóu}`; `:warichu[…]` with the note set smaller). Typing `:do`, `:na`, `:bo`, `:ru` or `:wa` offers the directive
- **Chip completion**: typing `:c` at the start of a word offers `:chip[…]` (the caret lands between the brackets). Inside `{style="…"}` the configured chip style ids are offered.
- **Label completion**: inside a `:::figure{resource=…}`, the value of `at="…"` in a `::label[…]` line offers the picture's anchors, each with the label style it gives, listing first those that no label of the figure stands at yet. Inside `style="…"` the label styles are offered, and `::la` at the start of a line writes a label line.
- **Small-caps completion**: typing `:sm` at the start of a word (or right after `*` / `_`) offers `:smallcaps[…]`, with the caret between the brackets.
- **Tabs**: `:tab` and `:tab{…}` are highlighted and offered in completion like the other inline directives. The editor's Tab key does not type a tab character, so `:tab` is how a tab is written in the Sandbox; a tab character pasted with the text is kept, and is a tab in a paragraph whose style sets tab stops.
- **Line numbers** and active-line highlighting
- **Line wrapping** for long paragraphs
- **Right-to-left lines**: a line whose text starts with an Arabic or Hebrew letter runs right to left, and the caret and the arrow keys follow it. The markup inside it (`{…}` attributes, `[^id]` labels, the name of a directive, a whole `:ref[fig-1]`, maths, code, link targets) stays left to right, so it reads as typed. Fences, front matter and lines that start in a Latin word are left to right as before.
- **Toolbar**: starts with **Undo / Redo**, followed by bold, italic, heading levels 1–3, link, inline code, **chip** (wraps the selection in `:chip[…]`), **small caps** (wraps it in `:smallcaps[…]`), in a Chinese, Japanese or Korean book **Ruby reading** (`:ruby[…]`) and **Across in vertical text** (`:tcy[…]`), blockquote, ordered and unordered lists, plus five inserters:
  - `:::pagebreak`
  - `:::space`: one line of vertical space (`:::space{lines=N}` for more)
  - **Columns**: a two-column `:::columns{count=2}` group, the caret on its first line, in the text or in a box (since postext 1.25)
  - `:::numbering{format="decimal" startAt=1}`: inserted with example attributes you can adjust
  - **Composed page**: opens a picker with the book's page templates, each drawn as a thumbnail of its empty frames. Choosing one writes a `::::page{template="…"}` fence with an empty `:::frame{slot="…"}` for each frame. A frame that only receives text continued from another frame gets none; a frame that `takes` a heading gets that heading, with the frame's name selected to type over, and the flow frame gets a blank line for the running text

A code listing (a ```` ``` ```` or `~~~` fence and its lines) is one region in the editor: it gets a code background, and the Postext markup inside it (maths, chips, tabs, index marks) is left uncoloured, as the engine leaves it unread. The editor also reports its cursor and selection to the previews, which enables click-to-caret sync: clicking text in the rendered output moves the caret to the matching Markdown position, and the previews can highlight the current selection.

The panel header is a **chapter switcher**: previous/next arrows, the chapter's position and title, and a menu listing every chapter of the book with its page range once its pagination is known (see [Books and chapters](https://postext.dev/en/docs/sandbox-books-saving.md#books-and-chapters)). Each chapter has its own editor instance, so undo history and caret position are kept per chapter. The header then offers five actions:

- **Reset** (asks for confirmation): reloads the **document part** of the open sample book. Every chapter and the whole figure set are replaced with the sample's (for the built-in sample, the locale's sample document and example resources). The design is left alone except for the sample's own fonts. See [Books panel](https://postext.dev/en/docs/sandbox.md#books-panel).
- **Export**: downloads the active chapter as a plain `.md` file, named `<book>-<nn>-<chapter>.md` in a multi-chapter book, and otherwise after the open book or `document.md`
- **Import**: replaces the active chapter with the contents of a `.md` file
- **Import a Word document**: opens a `.docx` manuscript and maps its Word styles to Postext styles before anything changes, or brings back the corrections made in a file exported from the book (see [Word documents](https://postext.dev/en/docs/sandbox-books-saving.md#word-documents))
- **Export to Word**: downloads the chapter or the whole book as a `.docx`, semantic (Word's own headings, lists, notes and tables) or formatted (the pages as the PDF prints them); the corrections made in it come back through **Import a Word document**

#### Books panel

The first panel in the activity bar: the gallery of your books and the sample books, with the **New** menu, the status of the last operation and the banner of a sample book that has a new version. In code and storage a book is still called a *project*, and a sample book a *preset*.

Clicking a book (or one of a sample book's language tags) opens it, and once it is on screen the sidebar moves to the [Chapters panel](https://postext.dev/en/docs/sandbox.md#chapters-panel). Clicking the book that is already open goes there straight away. The row menus (rename, duplicate, download, hide, delete) never change panels. The open book stays marked in the list. The panel has two groups:

- **My books**: local, editable books stored in the browser (IndexedDB). The open one is marked; its chapters are in the Chapters panel.

  While a book of your own is open, the working document *is* that book. Every edit to the text, design, figures and fonts is saved into it automatically (with the same 1-second debounce as `localStorage`), so you can switch books and come back without losing anything. You create books from the header's **New** menu:
    - **Copy of the open book** copies the open book, its figures and fonts into a new book.
    - **Blank book** starts an empty one.
    - **Open a .postext file…** imports a file.

  You can also duplicate any row. While you have no books of your own, the group shows an empty state with the same shortcut. Each row has one labelled **⋯** menu (*More actions for "…"*) with **Rename…**, **Add a language…** (see [A book in several languages](https://postext.dev/en/docs/sandbox-books-saving.md#export-and-import)), **Duplicate**, **Download (.postext)** and **Delete…**. Delete asks for confirmation right next to the ⋯ button.

  Rename opens inline name and description fields. Enter in either field commits both, and Escape cancels. On a book in several languages a box under them, **Only for the … edition**, gives the name to the language on screen alone.

  A book also carries a **cover image**, shown where a sample book shows its thumbnail. Making a copy of a sample book (or of another book) adopts its cover. While a row is being renamed, the cover is the control that sets it: click it to choose a PNG, JPEG, WebP, GIF or SVG, or use the X beside it to remove the current one. The cover belongs to the book, not to its figures, and travels with it through export and import.

  A book without a cover gets one from its **first page** (the first page of its first chapter): the first time that page is laid out and painted in the Canvas tab, the Sandbox takes a small picture of it (240 px wide, JPEG) while the browser is idle, once the fonts and the page's pictures are in. It is stored as the book's cover, so a download carries it as the bundle's `thumbnail`. A sample book that ships no thumbnail gets one the same way, kept in the browser (the `presetCovers` store of the IndexedDB database, one per sample and language), shown in its row and adopted by a copy or a download of it. An existing cover is never replaced, whether it came with the sample, with an imported file, from your own choice or from an earlier capture. A cover you remove is taken again from the first page on a later visit.

  Opening a book never asks for confirmation: the book you leave is saved first, whatever it is. While the next one loads, the viewport shows *Opening the book…* instead of the book you left. Deleting the open book falls back to the built-in sample.

- **Sample books**: starting points, listed below your books. The bundle itself is never changed, but your edits to a sample book are kept: the first change saves a copy of it (its *draft*) in the browser, one per sample and language, and the row shows an *Edited* tag. Opening that sample again, in that language, brings your version back; a sample you never changed opens as published. The ⋯ menu of each row offers:
    - **Make my own copy** turns the sample into a book of your own that holds its own copies of the bundle's files, so later edits to the bundle on disk never touch it.
    - **Download (.postext)** saves the sample as a `.postext` file.
    - **Hide from the list** removes the sample from the list (the built-in one cannot be hidden).
    - **Restore the original…** discards your changes to the sample (in every language) and, when it is open, loads the published version in its place. It asks first, and only appears on edited samples.

  A sample in several languages shows one tag per language, in the order its manifest lists them: 繁 and 简 for Traditional and Simplified Chinese, the language code (EN, ES) for the others; the tooltip names the language in the interface language (*Load the Traditional Chinese version*). A sample whose original is in another language than the interface (a Chinese novel with an English translation) says which one it opens in (`openLocale`, see [Preset bundle format](https://postext.dev/en/docs/sandbox-embedding.md#preset-bundle-format)), so a click on its row brings up the original.

  Hidden samples are collected under a *Hidden sample books* disclosure at the bottom of the group, each with **Show in the list**. The choice is remembered in `localStorage`.

A `.postext` file opened from the **New** menu becomes a new, open book. A file in several languages first asks which one to open, starting from the one it is written in (its manifest's `locale`), and the book keeps all of them (see [Export and import](https://postext.dev/en/docs/sandbox-books-saving.md#export-and-import)). Exporting is a per-row action: the open book's row exports what is on screen. A `.postext` file is a zipped preset bundle directory (see [Export and import](https://postext.dev/en/docs/sandbox-books-saving.md#export-and-import)).

The Reset buttons in the Text and Design panels restore the sample a book was copied from. A blank or imported book has no original, so the buttons are hidden while it is open.

#### Chapters panel

The second panel in the activity bar shows only the open book. At the top, its name, what kind of book it is (*Your book*, or *Sample book* with its language and an *Edited* tag once you change it) and a ⋯ menu with the actions on it: **Rename…** for your books (a double-click on the name does the same), **Duplicate** or **Make my own copy**, **Download (.postext)**, and **Restore the original…** for an edited sample book.

Below, its **chapters** fill the rest of the panel and scroll on their own (see [Books and chapters](https://postext.dev/en/docs/sandbox-books-saving.md#books-and-chapters)). Each chapter row shows its page range and word count, and the active chapter is marked. A Chinese, Japanese or Korean book counts characters instead: each Han character, kana or hangul counts one, and so does each Latin word among them. **Add chapter** sits in the panel header next to the chapter count, rows reorder by dragging their grip (or with the arrow keys on it), and each chapter's ⋯ menu offers **Rename**, **Move up / down**, **Split at level-1 headings**, **Merge into previous chapter** and **Delete** (the last chapter can never be deleted).

##### Sample books (presets)

A **preset bundle** is a whole starting point loaded as one unit: the chapters' Markdown, a `PostextConfig`, the resources (with their bitmap/SVG payloads and table models), and the custom font files the config refers to. The panel lists every preset the Sandbox can reach and marks the open one:

- **Built-in preset**: the *Postext guide*, the default sample book shipped with the package. It is always available, always first in the list, and **public**. It comes in English, Spanish, Catalan, Arabic, Simplified Chinese, Japanese, Brazilian Portuguese, French, German and Italian (the row's ES, CA, EN, ع, 简, 日, PT, FR, DE and IT buttons; `#preset=postext-guide&lang=zh-Hans` links to the Chinese edition, `lang=ar` to the Arabic one, `lang=ja` to the Japanese one, `lang=pt-BR` to the Brazilian Portuguese one). The Arabic edition is an Arabic book: right to left, bound on the right, Arabic-Indic digits, Amiri, Noto Kufi Arabic and IBM Plex Sans Arabic, kashida justification and figures drawn in mirror with Arabic labels. The Chinese edition has a design of its own (Noto Serif SC and Noto Sans SC, mainland punctuation and line breaking) and figures drawn with Chinese labels. The Japanese edition is a vertical book too, set by the Japanese rules (JLReq): Noto Serif JP and Noto Sans JP, 9.25 pt type on two tiers of 34 characters, section headings three lines deep (行取り), running heads and Arabic folios across the head and foot, figures with Japanese labels. Each interface opens the guide in its own language. An untouched Chinese, Japanese or Arabic guide opened from another interface (its button or a `lang=` link) stays in its language whatever the interface language; the one the Chinese, Japanese or Arabic interface opened itself changes to the language the Sandbox is next opened in, like the other editions.
- **Remote presets**: bundles served over HTTP from the base URLs passed in the `presetSources` prop (see [Props](https://postext.dev/en/docs/sandbox-embedding.md#props)). Each source is either public or **private**. Private presets are tagged *Private* in the list and are meant for material that must not leave a machine. The public website only lists a private source in local development, where the Next.js app serves the folder named by `POSTEXT_PRIVATE_PRESETS_DIR` through `/api/private-presets`. Production never lists it. The website's public source, `/presets`, serves its sample books: *Bioquímica*, *Deep Sky*, *Don Quijote*, *Physics (OpenStax)*, *Spanish Painting* and *Signals 2020* in Spanish and English, *Dream of the Red Chamber* in three editions, *Paradise Lost* in English, with Verity’s notes as footnotes and Doré’s plates, *One Thousand and One Nights* in Arabic, *Kokoro* in Japanese and the comic *Pepper&Carrot* in ten languages. Each comes with its licence and credits.
- **`default: true`**: a private preset flagged as default is loaded automatically the first time a *pristine* Sandbox (untouched built-in document) opens. When several are flagged, the one whose `locale` matches the UI locale wins. Public presets are never loaded automatically.

Each row shows the preset's name, an optional description, its locale tag and its *Private* / *Default* tags. Clicking a row loads the preset **in full**: text, figures, design and fonts. The Sandbox first writes the font files and figure data to IndexedDB, so the viewports never see a half-applied document. Clicking a row opens the sample in the interface language, unless you have only edited it in another language: then your most recent version opens. A bilingual sample's language tags open it in that language (your version there, if you have one).

**Live presets.** Remote presets are followed automatically. Whenever a preset is applied, the Sandbox records the bundle's *fingerprint* along with hashes of the Markdown, configuration and resource set it applied. While the page is visible, it polls the fingerprint every 3 seconds, and immediately when the tab becomes visible or the window regains focus. When the fingerprint changes:

- If the book is **untouched** (Markdown, configuration and resources still hash to what the preset applied), the preset is re-applied in full and a brief notice appears in the panel.
- If there are **local changes**, they are kept. A banner at the top of the panel (*This sample book has a new version. Your changes are kept.*) offers **Load the new version**, which asks before it discards your changes to that language of the sample, and the Books button in the activity bar shows a dot meanwhile. The draft remembers the version it was made from, so the banner comes back each time you open it until you load the new version. Figures of the draft whose files changed in the new version keep the bytes they had: the draft gets its own copy of them before the bundle's are written. Bundles are only followed while a sample book is open. A book copied from one keeps its own copy.

The same check runs on load, so if you edit a bundle and then open the Sandbox, it picks up the new version as long as the book was untouched. The fingerprint comes from `<preset-dir>/fingerprint.json`. On the development route this is a virtual file that returns a metadata-only hash of the directory (see the [Preset bundle format](https://postext.dev/en/docs/sandbox-embedding.md#preset-bundle-format) appendix). Sources that do not serve it are not followed.

The open sample book also defines what the other panels' **Reset** buttons restore:

| Panel | Reset restores |
| --- | --- |
| Text | The sample's text and its figure set (the document part) |
| Design | The sample's design, its fonts included |
| Books (Restore the original…) | Everything: text, figures, design and fonts |

A preset that was open in an earlier session but is no longer listed by any source (typically a private preset in a build that does not serve it) still appears, greyed out as *Not available in this build*. The Sandbox keeps the loaded content instead of silently swapping it for the built-in sample. The open preset's id is persisted under the `postext-sandbox-preset` key (see [Persistence](https://postext.dev/en/docs/sandbox-books-saving.md#persistence)). The bundle format is described in the [Preset bundle format](https://postext.dev/en/docs/sandbox-embedding.md#preset-bundle-format) appendix.

#### Resources panel

Manages the book's resources: bitmap images, SVG figures, tables and videos. The text refers to them with `::resource{id="…"}` blocks and `:ref{id="…"}` inline references. For the reference syntax and the float model, see [Document format → Resources](https://postext.dev/en/docs/document-format-resources.md#resources).

**List view.** The header shows the number of resources. Resources are grouped by their resource-type name, and each group is a counted list. Resources whose `typeId` no longer matches a configured type fall into an *Untyped* group so you can still reach them. Each item shows a 36 px thumbnail next to its caption and id: a real preview for bitmaps and SVGs, the poster with a play badge for videos, and a glyph for tables. When the book has more than six resources, a **search** field (*Search resources…*) filters the list by caption and id, ignoring accents. When the book has no resources yet, an empty state explains how to add them and place them in the text with `:ref{#id}`. A **New** dropdown offers four ways to add one:

- **Upload image**: PNG, JPEG, WebP or GIF
- **Upload SVG**
- **New table**: starts as a 2×2 grid with one header row
- **Add video**: a YouTube or Vimeo video, a video file, or the address of one (an MP4 or an HLS stream; see *Video* below). A book whose resource types predate videos gets the built-in *Video* type when it adds its first one.

You can also drag and drop files from disk onto the panel, video files (MP4, WebM, Ogg, QuickTime) included. They upload in bulk, with ids slugified from their filenames. Unsupported files are skipped, and a short note reports how many were added and how many were skipped.

**Detail view.** Selecting a resource opens its editor:

- **ID**: editable and slug-validated. Duplicate ids are flagged and never committed, and an empty id gets a suggestion derived from the caption. Renaming keeps the stored image or SVG file.
- **Type**: one of the configured resource types
- **Caption**: supports inline Markdown (bold, italic, code and `:ref`). `\\`, or a backslash ending a line, starts a new line; a plain line break counts as a space
- **Note**: an optional source line, credit or remark, set in a smaller run under the resource (same inline formatting and line breaks as the caption). It is styled by the *Note* group of the *Captions* settings.
- **Alt text**: accessibility description for the HTML output
- **Table style**: for tables, the named style the table is set in (`table.styleId`). This is either *Document table style* (the default) or one of the *Table styles* declared in the settings. An id that no style declares is listed as missing and renders with the document table style.
- **Placement**:
  - **Position**: `auto` (the first free slot, the default), `top`, `bottom` or `here`.
  - **Span**: `column`, `page` or `side`. It is disabled when the position is `here`. The defaults match the engine's, `auto` + `column`.
  - **Columns**: how many adjacent columns a column float takes on a page of several columns (`placement.columns`), such as a picture across two of a newspaper's four. As many as the page has makes it page-wide. It shows when the book or one of its sections has more than one column and the span is `column`, and not for a resource set `here` or turned.
  - **Orientation**: upright, or turned a quarter turn (top to the left, or to the right). A turned resource takes a page of its own, flush to the spine, so the span select is disabled.
  - **Width**: a width fraction with an alignment, for floats and inline embeds narrower than their column. The alignment also places a bitmap or SVG narrower than its slot at full width (a bitmap smaller than the column, or a figure `layout.fitFiguresToPage` shrank), so it stays enabled for pictures; for a table it needs a width under 100%. A turned resource has neither.
  - **Caption beside the figure**: a toggle for the side column of a column-and-a-half layout. It applies to column floats, so it is hidden when the position is `here`.
  - **Shrink to fit**: for a floated picture, *Never*, *On a fresh page* or *In any slot* (`placement.shrink`), with **Smallest scale** (`placement.minScale`) once it shrinks; and **Caption width**, *Slot* or *Picture* (`placement.captionMeasure`), for any picture that is not turned. Resource types offer the same three fields in their default placement.
  - **Head the page that cites it**: for a top or automatic float of a column or page span that is not turned (`placement.citingPage`): it may take the head of the page or column where the line citing it lands, the text above the reference moving down under it. Resource types offer the same switch in their default placement.
  - **Text wrap**: for a resource set in the text or a one-column float, *None* or the side of the column it sits on with the text beside it (`placement.wrap`), and its **Wrap gap** (`placement.wrapGap`) once it wraps; a wrapped resource with no width of its own shows the layout's default share. Resource types offer both in their default placement. *Contour*, for a picture, runs the text round its shape at the side its *Alignment* names (`wrap: "contour"`): a bitmap then shows **Shape from transparency** (`contour.alpha`), its **Opacity threshold** (`contour.threshold`) and the **Contour margin** (`contour.margin`), and the Sandbox traces the alpha channel from the stored file. A picture with no known shape says so under the fields.
- **Payload**: uploaders that replace the bitmap or SVG file
- **Size and resolution**: for a bitmap, its pixels, the resolution its file states (*none stated*, or 72 and 96 marked as read as unset), its natural size in mm at the resolution it is laid out at, and the ppi it prints at where the current layout places it (the lowest when it is placed more than once). Under the preflight's minimum it shows in the warning style, under the critical one in the critical style, with **Open in Checks** for a book set up for print. **Resolution** sets the picture's own ppi (`bitmap.resolution`; see [Document format › Bitmap size](https://postext.dev/en/docs/document-format-resources.md#bitmap-size)), and **Use the file's … ppi** fills it in from the file. An uploaded picture keeps the resolution its file states (`bitmap.fileResolution`).
- **Video**: for a video resource ([Document format › Videos](https://postext.dev/en/docs/document-format-resources.md#videos)):
  - **Source**: *YouTube*, *Vimeo* or *File*.
  - **Video address** (YouTube, Vimeo): any link of the video. When it names a new video, its poster, frame size and title are fetched from the platform (the title becomes the alt text when that is empty); **Fetch the poster again** repeats it. A link that names no video of the platform is flagged under the field.
  - **Video file** (File): the uploader of an MP4, WebM, Ogg or QuickTime file, with its format, size and length. A first poster is taken a tenth of the way in. **Poster frame** plays the file: stop or scrub on the frame you want printed and press **Use this frame as the poster**. **Production address**: where the published video will be (`https://…`), opened by the QR code and the PDF link; without it print gets no QR code or link.
  - **Video address** (File, with no file uploaded): the video plays from its address alone, an MP4 or WebM on a server or an HLS stream (`.m3u8`, marked *HLS stream* above the field). Entering an address reads the video's size and length, and **Poster frame** plays it from there, HLS included. Picking a frame needs a server that allows cross-origin reads (CORS); otherwise the panel says so, and the poster is uploaded instead. The QR code and the PDF link open this address.
  - **Poster**: the printed picture with its size, and an uploader to replace it with a picture of your own.
  - **Start (s)** / **End (s)**: the part that plays; 0 leaves the start or the end as it is.
  - **Player**: this video's switches (controls, download button, full screen, speed menu, picture in picture, autoplay, muted start, loop, play alongside other videos), each *Book setting*, *Yes* or *No*. Only the switches the video's player honours are listed.
- **Safe area**: for bitmaps and SVGs, the part of the picture that is always shown (`Resource.safeArea`; see [Document format › Safe area](https://postext.dev/en/docs/document-format-resources.md#safe-area)). The field shows a thumbnail with the area marked and its size in percent, or *None* when the picture has none, with **Mark safe area** / **Edit safe area** and **Remove safe area** buttons. The dialog shows the picture: drag outside the rectangle to draw a new one, inside it to move it, or from its handles to resize it; with the rectangle focused, the arrow keys move it and Shift with the arrows resizes it. *Left*, *Top*, *Width* and *Height* fields set it in percent, and two previews show the tallest and the shortest crop the area allows.
- **Anchors**: for bitmaps and SVGs, the points of the picture that a map's or a diagram's labels stand at (`Resource.anchors`; see [Composed pages › Figures with labels](https://postext.dev/en/docs/composed-pages-figures.md#figures-with-labels)). The **Anchors** button of the same field opens the dialog in its *Anchors* mode, and the field counts the anchors and the labels still waiting for one.
  - **Placing by clicks.** Under *Labels without an anchor* the dialog lists the names that the text's `::label[…]{at="…"}` lines use and the picture has no anchor for, and picks the first one: a click on the picture places it there and picks the next, so a map whose labels are already written gets its anchors one click each. Pick another name, or **Stop placing**, at any time. With no name picked, a click adds `anchor1`, `anchor2`… and puts the cursor in its name.
  - **Each anchor** has its **Name** (the id the labels give), a **Label style** of the design (`labelStyles`), an **Alignment** (start, center or end, in the label's direction), a **Vertical alignment**, a **Rotation** in degrees and a **Width**, a share of the picture's width that the label wraps within (0 keeps its natural width). A new anchor takes the label fields of the one picked before it, so a row of towns shares one style. The list shows each anchor's label text in the chapter on screen (*No label in the text* when there is none) and a filter field when the picture has more than eight.
  - Drag a point to move it; with a point focused, the arrow keys move it (Shift for larger steps) and Delete removes it. Anchors are fractions of the whole picture, so they keep their place whatever crop the safe area allows; one outside the safe area is flagged. **Save** writes them onto the resource as one step of the undo history.
  - **On the page.** In Design mode the Canvas shows the box of every drawn label (in red, marked *overflows its width*, when it does not fit even at its style's smallest size) and its anchor's point. Drag the point or the label to move the anchor: the label is set again where you leave it, and Cmd/Ctrl+Z takes the move back. A click opens the picture in this panel; a double click opens its anchors on that one. A label placed with `at="x% y%"` moves the same way, and where you leave it the `at` value is rewritten in the text of the edition on screen: the percentages are that edition's own words. In Content mode a click on a label selects its text in the Text panel.
  - **Several at once.** Each row of the list has a checkbox, and a Shift- or Ctrl-click on a point of the picture ticks it too; **Select shown** ticks every row the filter leaves. The ticked anchors show one set of fields (style, alignment, vertical alignment, writing), and a change reaches all of them as one step of the undo history. Where they differ, the field says *Mixed*.
  - **Dense maps.** From 24 anchors on, a point shows its name only when it is picked, hovered or ticked, so the tags do not pile up; **Names on the picture** shows them all again. **Writing** sets an anchor's labels vertically (`writingMode`), and its **Width** is then a share of the picture's height.
  - **Overlap.** *Check* is the default. *Allow* (`overlap: 'allow'`) says the source prints this anchor's label touching its neighbours, as a dense map does, and the Checks panel stops raising `labelOverlap` for any pair it belongs to. The ticked anchors take it together. A label style can allow it for all its labels (**Design › Figures & tables › Label styles**). Since postext 1.42.
  - **Spine.** *Keep off* is the default: on a figure across the spine of a spread, a label that would reach into the gutter band stands at the band's edge, on the side of its anchor. *Cross* (`gutter: 'cross'`) leaves it where the point puts it, as a title across both pages needs. The ticked anchors take it together. Since postext 1.43.
  - **Zoom.** The buttons above the picture, `+`, `-` and `0` on the keyboard, and Ctrl-scroll or a pinch zoom it in steps up to 800 %, about the point under the pointer. A zoomed picture moves with the scroll wheel, with the hand button, or with Space held down (or the middle button) and a drag. The points keep their size, and from 300 % a dense map shows every name (since postext 1.40). On a touch screen two fingers move the picture and spread or pinch to zoom it about the point between them (since postext 1.42).
  - **In the HTML viewer** Design mode draws the same boxes and points and the marks are moved as on the Canvas: drag a point, and the anchor (or the percentages of an `at`) is written as one step of the history (since postext 1.40). **In the Folio**, in select mode, the marks are drawn on the pages of the book and dragged on them as on the Canvas: the point follows the pointer on the page's texture, the drop writes the anchor, and a press on a mark never turns a page (since postext 1.42). On a touch screen a finger takes a label by its box, or within a fingertip of its point, and drags it from there; a finger on bare paper still scrolls the page (since postext 1.43).
- **Fonts in the drawing**: for SVG figures, the families the drawing's text names (family, weight, style) and what became of each: *embedded* with its size, *declared in the SVG* (the file carries its own `@font-face`), *no font file* (the image falls back to another face) or *over the size cap*. The faces come from the book's custom fonts, then from Google Fonts, and are embedded only when the picture is shown or exported, never in the stored file. **Embed the book's fonts** turns it off for this figure (`svg.inlineFonts: false`), for an SVG that carries its own faces or must stay as it is.
- **SVG source**: for SVG figures, a source editor under the uploader. Only the text nodes accept edits: the content of `<text>` / `<tspan>` elements, tinted in the editor. Typing anywhere else is rejected, and `<`, `>` and `&` typed into a text node are escaped so the file stays well-formed. Each change is saved after a short pause as a **new** stored file, and the figure re-renders (single-ink recolouring included). The editor notes when the SVG has no text nodes.
- **Preview**: a live mock-up of how the resource will look, with `#` standing in for the number (real numbers depend on where the resource lands in the document). The caption opens with the label the document sets: the type's caption prefix and the number (`Figure #.`), the prefix alone for a type numbered with an empty template (`Do.`), and nothing for a type with no caption prefix
- **Delete**: asks for confirmation first, and warns how many `::resource` / `:ref` references in the text would be left pointing at nothing

**Table editor.** Table resources open an interactive grid editor:

- **Toolbar**:
  - Add or remove rows and columns, and merge or split cells.
  - Toggle a header row or a header column.
  - Set each cell's horizontal and vertical alignment (top, middle or bottom). The cell content moves as one unit inside a taller row.
  - Set each cell's fill: a colour picker, palette-linkable, with a clear button. The fill is written to `TableCell.background` and shown in the grid.
- **Column widths**: one numeric weight per column under the grid (`2, 1, 1` gives the first column half the width; empty means equal widths), written to `table.model.columnWidths`. Adding or removing a column keeps the weights aligned, and an *Equal widths* link clears them.
- **Paste as table**: pasting TSV (or simple quoted CSV) fills the grid
- **Undo/redo**: Cmd/Ctrl+Z and Shift+Cmd/Ctrl+Z
- **Keyboard navigation**: Tab / Shift-Tab (with row wrap) and the arrow keys move between cells
- **Click to edit from the preview**: click a table cell, a caption, a note, or a text fragment of an SVG figure on either preview tab. The Resources panel opens on that resource and focuses the matching field, with the caret on the clicked glyph. Dragging selects a range inside the same cell or field, and a double-click selects the word. While the field has focus, its selection is mirrored back onto the page as a highlight (a thin caret when collapsed) on both the Canvas and HTML tabs. A caption or a note that a `:::figure` writes in its own words (`::caption[…]`, `::note[…]`) is selected in the Markdown instead, where it is written.

All edits go through pure model functions exported by the postext core (`addRow`, `addColumn`, `removeRow`, `removeColumn`, `mergeCells`, `unmergeCell`, `setCellContent`, `setAlignment` and `parseTSV`), so the same table model behaves identically inside and outside the Sandbox.

#### Fonts panel

Shows the typefaces the book uses and manages your own font files alongside the built-in Google Fonts catalogue:

- **Typefaces in this book** lists every family the design uses. Each family name is set in its own typeface, with its role (*Body text*, *Headings* or *Other elements*) and its source: *Google Fonts*, *Fontsource* (a family Google Fonts does not have), *Your file*, or *Not found* for a family no source has. The source appears once the loader knows it. You change the typefaces themselves in **Design → Typography** and **Headings & contents**.
- **The files a book is measured with.** A family of Google Fonts or of Fontsource is set from Fontsource's static files on jsDelivr (`@fontsource/<family>@5`), one per weight, style and subset. They are the files a Cookbook pen loads and the PDF embeds, so a book breaks its lines at the same words in the previews, in its PDF and in a pen. The command line lays out with `bodyText.shapingLanguage: 'host'`, so a line can break at another word there (see [Differences from the Sandbox](https://postext.dev/en/docs/command-line.md#differences-from-the-sandbox)). The previews declare the weights and styles the design and the pages use, and the browser loads each file when the text needs it. Since postext 1.30 the previews also wait, before they first paint a chapter, for every face its laid-out pages name, loaded for the characters of those pages (a family cut into more than 16 files per face, a Chinese or Japanese one, loads them as before). From postext 1.28.3 to 1.29.2 a face only the pages name, set in a script other than Latin (the italic of the summaries of an Arabic contents page), was painted in a fallback face until its file arrived. A weight between two cuts of a variable family (a 450) has no static file: the previews set it from Google's variable file at that weight and the PDF embeds the nearest cut, so choose a weight the family has when the two must agree. A family Fontsource has no files for is taken from Google's style sheet. Every file of a family comes from one version of its package, the one that served its style sheet on jsDelivr: `@5` names whatever version each address resolves to, so after a release that cuts a family again, its files could otherwise come from two cuts.
- **A file that is slow to arrive, or never arrives.** The files of a book's faces are fetched before its first layout, and the layout waits for them as long as they keep arriving: a book of several hundred files (a Chinese or Japanese one) on a slow connection is laid out once, with all of them. The requests are given up only when no font file has received anything for 15 seconds; the chapter is then laid out with the faces that have arrived, the others in the fallback its warnings name. Such a layout is provisional: its page counts are not stored with the book nor written to a bundle. The file is requested again three times, half a minute apart, and by every later layout; when it arrives, the previews and every chapter laid out without it are laid out again, contents and page references with them, and the PDF and the EPUB are marked out of date.
- **A weight or slant the family does not have.** The previews and the PDF set each family in its own files. Where a family has no bold (Bangers, Zen Antique and most display faces ship one weight) or no italic, the bold and the italic are set in its nearest face as it is, without the heavier or slanted letters a browser would draw, and no `fontFallback` is listed for them. A weight that another real face of the family stands in for (a 600 set from the 700) is listed. A family Fontsource carries that Google Fonts does not, such as Commit Mono, is loaded from Fontsource.
- **Your font files** is where you add, rename and delete custom families.
  - **Upload file**: variant files per weight (100–900) and style (normal / italic). Accepted formats are WOFF2, WOFF, TTF and OTF, detected by file extension. The files are stored in the browser's IndexedDB.
  - Custom families appear in every font picker under a **Custom** group alongside the Google Fonts results. A custom family **takes precedence** over a Google Font with the same name.
  - In a Chinese document every font picker lists first, under *Simplified Chinese* or *Traditional Chinese*, the Google families that hold the document's characters (Fontsource's `chinese-simplified`, `chinese-traditional` and `chinese-hongkong` subsets), then the rest.
  - In a document in Arabic (or another language written in the Arabic script) every font picker lists first, under *Arabic*, the Google families with Fontsource's `arabic` subset, the book faces first (Amiri, Noto Naskh Arabic, Scheherazade New, Markazi Text, Lateef, then Noto Kufi Arabic, Reem Kufi, Aref Ruqaa, El Messiri). The previews load each face's Arabic file before the first layout, and the PDF embeds it with the Latin one when the text uses both.
  - Orphaned font files (uploads no longer used by any family) are removed from IndexedDB automatically.

### Phones and small screens

Below 768 px wide the Sandbox switches to a layout of its own:

- **Panels.** The panel bar moves to the bottom of the window: a **Preview** button first, then the same buttons in the same order, in two rows of four below 352 px. An open panel covers the preview instead of sitting beside it; the preview stays where it was underneath, out of the tab order, so closing the panel finds it on the same page.
- **Top bar.** The output tabs, the scope selector (the rendition choice on the EPUB and SCORM tabs) and the host's controls (large targets, theme, language) share the bar above the preview. Below 640 px it takes two rows: the logo, the scope and the controls above, the tabs below, sharing the full width; five tabs fit at 320 px.
- **Toolbars.** The floating toolbars of the previews dock along the bottom of the preview and wrap onto a second row when they do not fit. The editions rail of the Canvas is a row at the top of the preview.
- **Folio.** The Folio tab is not offered on a phone's screen (see [Folio tab](https://postext.dev/en/docs/sandbox-previews.md#folio-tab)).
- **PDF.** Phones and touch-only tablets cannot show a PDF inside the page (Android has no inline viewer, and iOS draws only its first page), so the PDF tab shows a card with **Open PDF** instead, which opens the file in a new tab; the toolbar's **Download** still saves it.
- **Touch.** Moving a label with a finger on the Canvas and the HTML viewer (since postext 1.43), the Folio's select mode and the two-finger pinch and pan of the anchors dialog are tested with the browser's touch emulation and real touch events, on a phone-sized and a tablet-sized screen. No physical device has been tried: a browser that reads `touch-action` differently may scroll the page where the Sandbox expects a drag. If a gesture misbehaves, report the browser and the device.

![Two phone screens: on the left the Canvas with a page of the book and the panel bar along the bottom, on the right the Text panel covering the preview.](https://postext.dev/docs-shots/en/sandbox-phone.webp)

*On a phone: the preview with its toolbar docked at the bottom, and the Text panel open over it.*

## Roadmap

The following features are planned for future versions:

- **Collaborative editing** — Edit one book together from several browsers
