Blocks
A block is one paragraph-level unit of the content stream. Its kind is
inferred from its spelling in the source syntax (see
Kind inference), and its shape determines what content it
holds (see the three shapes). The block types
available to a script — their labels, styling, behavior, and flow — are declared
in config.blocks as an array of BlockDef. Even media —
image, audio, video — is just void-shaped block types declared here; no field
below is media-specific.
BlockDef
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
✓ | Unique kind id. One class-safe token. |
label |
string |
Display label; default: the id. | |
shape |
"text" | "void" | "grid" |
Content shape; default "text". |
|
grid |
{ cols? } |
Default column count when authoring a new instance of a grid-shaped kind. |
|
default |
boolean |
true on exactly one block — the default Text block. |
|
behavior |
BlockBehavior |
Structural flags (below). | |
indexes |
IndexDef[] |
Navigator index groups this kind feeds. | |
editor |
BlockEditor |
Editing behavior — shortcut, placeholder, Enter/Tab flow, allowed sections. | |
style |
StyleDecls |
CSS-subset declarations — the canonical home of a kind’s styling. | |
syntax |
object |
Per-syntax spelling (match + write), keyed by syntax id. See Kind inference. |
The default Text block
Exactly one block type must carry default: true. It is the fallback for any
content that matches no other kind — a bare paragraph with no distinguishing
spelling and no styling of its own. It is the implicit final fallback of kind
inference and is never listed in match precedence (it declares no match
rules). A config with zero, or more than one, default block is invalid.
BlockBehavior
Structural flags — all optional. Together they express a block type’s role in the document’s outline, numbering, and pagination.
| Field | Type | Default | Description |
|---|---|---|---|
outline |
boolean |
false |
Show blocks of this kind in the outline / navigator. A kind with outline is a division. |
rank |
number |
Outline nesting rank (1 = top). Only meaningful with outline. Division structure lives here. |
|
numbered |
boolean |
false |
Automatic running number (e.g. scene numbers). |
restartNumbering |
boolean |
false |
On a numbered division, restart the count at 1 whenever a coarser division (lower rank) appears — a play’s scenes reset each act. Off = the count runs continuously (screenplay style). |
summary |
boolean |
false |
Summarizes the division it opens (the navigator shows an auto-blurb). |
printing |
boolean |
true |
false = the block exists in the file but never prints or renders for reading — writer-only notes (Fountain [[…]], boneyard /* … */). |
keep |
object |
{ together?, withNext?, withPrev? } — typography keeps: don’t break inside the block, or don’t separate it from its neighbor (a cue never orphaned from its dialogue). |
|
more |
string | true |
Text printed at the foot of a page when this block breaks across pages; true = the default "(MORE)". Localizable. |
|
resumed |
string | true |
Text appended to a continued cue — after a page break, and on a consecutive same-index block of this kind; true = the default "(CONT'D)". Localizable. |
There is no speaker / cue flag. Speaker identity is derived from
indexes— the character list is just an index group — and same-speaker(CONT'D)falls out ofresumedcombined with index-match equality on consecutive cue blocks. FDX character mapping is an adapter requirement, not a format flag. See the fdx adapter.
Divisions (blocks with outline + rank) are the repeating, ranked headings —
Act, Sequence, Scene, Chapter — that form the outline. They are a distinct concept
from sections; see that page for the full distinction.
Example — a screenplay scene heading
{
"id": "scene_heading",
"label": "Scene Heading",
"behavior": { "outline": true, "rank": 3, "numbered": true, "summary": true },
"indexes": [
{ "label": "Locations", "pattern": "^(?:INT\\.?|EXT\\.?|I/E)\\.?\\s+(.+?)(?:\\s+[-–—]\\s+.*)?$" }
],
"editor": { "shortcut": "Mod-1", "enter": "action", "tab": "action", "shiftTab": "transition", "sections": ["body"] },
"style": { "text-transform": "uppercase", "font-weight": "bold", "margin-top": "1.6rem" },
"syntax": {
"html": { "match": [{ "construct": "h3" }], "write": { "tag": "h3" } },
"fountain": { "match": [{ "prefix": "INT." }, { "prefix": "EXT." }], "write": { "transform": ["upper"] } }
}
}
IndexDef
An index is a navigator sidebar group — a list of entries collected from the text of blocks of a kind. A character list, a location list, a props list: each is an index.
| Field | Type | Required | Description |
|---|---|---|---|
label |
string |
✓ | Group label in the sidebar (e.g. "Characters"). |
pattern |
string (regex) |
✓ | Regex source run against the block’s text; matches collect into the group. |
A block type may declare zero or more indexes. Multiple block types can feed the
same label (they merge under one group). This is how the screenplay’s character
block populates a Characters index — and, combined with
behavior.resumed, how same-speaker (CONT'D) is derived.
BlockEditor
The editing behavior of a block type — its keyboard shortcut, its placeholder, its Enter/Tab flow, and where it’s allowed.
| Field | Type | Description |
|---|---|---|
shortcut |
string |
Keyboard shortcut, e.g. "Mod-1" (Mod = ⌘ on macOS, Ctrl elsewhere). |
placeholder |
string |
Ghost text shown in an empty block. |
enter |
string |
Kind of the next block created on Enter (a block id). |
tab |
string |
Kind this block cycles to on Tab. |
shiftTab |
string |
Kind this block cycles to on Shift-Tab. |
sections |
string[] |
Allowed section ids (sectioned documents only). Omitted = anywhere. Edge blocks are exempt. |
enter / tab / shiftTab are what make a screenplay editor feel like a
typewriter: press Enter after a Character cue and you’re in Dialogue; press Tab and
Scene Heading cycles to Action. The flow is entirely format data.