Attributes
Blocks, marks, inline objects, and grid cells carry a fixed vocabulary of
attributes — plus one escape hatch, data. The vocabulary is closed by
design: the model stays predictable, and every consumer knows exactly what it
must round-trip. Attribute persistence is tiered — some attributes
survive only in syntaxes that can store them.
The vocabulary
| Attribute | On | Description |
|---|---|---|
class |
any block, mark, inline object, or grid cell | Authored CSS classes, space-separated. These are element identity for publisher styles, not ScriptPub block kinds (a kind’s own selector classes are consumed, not stored here). |
style |
any block, mark, inline object, or grid cell | Safe element-local CSS declaration text, serialized as the HTML style attribute — direct formatting for one occurrence, including per-instance break-* fragmentation. |
role |
any block, mark, inline object, or grid cell | HTML/ARIA role. |
epubType |
any block, mark, inline object, or grid cell | EPUB structural semantics, serialized as epub:type; a space-separated token list. |
aria |
any block, mark, inline object, or grid cell | ARIA attributes keyed without the aria- prefix, for example { "label": "Chapter ornament" }. |
lang |
any block, mark, inline object, or grid cell | Language override (BCP-47). |
dir |
any block, mark, inline object, or grid cell | Direction override — "ltr", "rtl", or "auto". |
data |
any block, mark, inline object, or grid cell | Custom escape hatch — a free { key: value } map, serialized as safe data-* attributes. |
id |
any block | Link-target id; persisted only when targeted or pinned (see Links). |
src, alt |
void (media) blocks | Media source (a reference, never bytes) and accessible description. |
type |
void (media) blocks | Explicit media type (MIME); usually inferred from the src extension. |
width, height |
void (media) blocks | Intrinsic dimensions in CSS px (integers) — a content fact; presentation sizing stays in style. |
poster |
video blocks | Poster frame (the src reference grammar). |
controls, loop, muted |
audio/video blocks | Playback flags. controls defaults true; the others false. |
narration |
any block | { src?, start, end } — a narration clip: this block’s slice of the work’s narration audio, in seconds. src defaults to meta.narration. |
list |
text blocks | { type: "ordered" | "unordered", depth?, start?, value? } — flat list metadata; start carries an ordered list’s first ordinal and value an explicit item ordinal. |
grid |
grid blocks | { headerRows?, headerCols? } — the leading N rows / first N cells render as <th> (a11y + Markdown table headers). |
colspan, rowspan |
grid cells | Cell spans. |
href |
marks | Link target — an external URL or an internal ref. |
rt |
marks | Ruby annotation text (rendered <rt> inside a ruby mark; converters without ruby may render it parenthesized or drop it). |
title |
marks | A link title ([text](href "title") ↔ <a title>) — CommonMark parity. |
origin |
any block (in translation documents) | A block ref (^id) into the original this block translates — the alignment anchor. |
originRev |
any block (in translation documents) | The origin block’s revision marker when last aligned — staleness tracking. |
rev |
any block or mark | A revision id (config.revisions[].id) — “touched in this pass.” On ins / del marks it yields full tracked changes. |
There is deliberately no pagination attribute: forced breaks, keeps,
and their instance overrides are real CSS break-before / break-after /
break-inside declarations — in the document sheet per
kind, or in an instance’s style attribute for one occurrence.
Attributes split by where they live:
ElementAttrs = { class?, style?, role?, epubType?, aria?, lang?, dir?, data? }
BlockAttrs = ElementAttrs & { id?, src?, alt?, type?, width?, height?,
poster?, controls?, loop?, muted?, narration?,
list?, grid?, origin?, originRev?, rev? }
MarkAttrs = ElementAttrs & { href?, rt?, title?, rev? }
InlineObject = ElementAttrs & { type: "image", src, alt?, title?, width?, height? }
CellAttrs = ElementAttrs & { colspan?, rowspan?, header?, align? }
An inline image carries its
src / alt on the run’s object, not in a mark attribute bag. It still
shares ElementAttrs, so a publisher’s drop-cap image can retain its class,
role, EPUB semantics, ARIA, language, direction, and custom data
independently of the paragraph and any enclosing link.
A MarkUse is a bare id when it carries no attributes, and the object form
{ id, attrs } when it does — see runs.
Tiered persistence
The .spub.html file stores the complete vocabulary natively — class,
style, role, epub:type, aria-*, lang, dir, id, data-*,
data-rev, data-origin, data-narration, and the media attributes are all
plain HTML attributes.
Foreign syntaxes do not. Content and kind round-trip losslessly through every conversion, but attributes are tiered — a round-trip preserves content and kind, not necessarily every attribute:
- Attribute-capable formats (FDX partially, office formats partially) keep what they can express.
- Content-derived syntaxes (plain text, Fountain, most of Markdown) have
nowhere to put an attribute without polluting the text, so they simply
don’t carry it. The media attributes
degrade the same way —
src/altonly in Markdown, alt-text-or-nothing in the plain-text syntaxes.
This is a deliberate trade: the price of a plain-text or Fountain export that stays readable and editable in any tool is that it can’t silently smuggle metadata. The things a reader actually sees — the words and their block kinds — always survive.
A note on origin vs src
Both are block attributes and both look like references, but they are unrelated:
srcis a media source, on avoidmedia block.originis a translation alignment anchor, on a block in a translation document — a block ref into the original.
See Translations.