Skip to the document

Core blocks

The blocks the app ships with: page-break, toc, children, image, callout, card, columns and ts-block. Unlike .block packages, they run outside the sandbox, need no approval and their names can't be reassigned by a vault's aliases. Each has the id @core/<name>, so a fence can name one either way: ```toc or ```@core/toc.

This page is every field as it's written in the file. Blocks is the plain version: a page for each block, with a picture of its form and what each option does on screen.

There's no table block. A table is an ordinary markdown table, which the app edits as a grid (Writing and editing). The block was removed on 16 September 2026, and a fence still named table is an unknown block whose error says to write a markdown table instead.

The @core scope

Every core block has an id in the reserved @core scope, alongside its short name:

Name Id What it is In the app
page-break @core/page-break Starts a new printed page Page break
toc @core/toc Table of contents from the headings Table of contents
children @core/children Links to the pages below this one Child pages
image @core/image An image from assets/, with a caption Image
callout @core/callout A note, tip, warning or quote in a box Callout
card @core/card Markdown in a box Card
columns @core/columns Markdown items laid out in columns Columns
ts-block @core/ts-block TypeScript written inline Not in the list of blocks to add; it's written in the Source view

A fence, a vault.json alias or a frontmatter pin can use either form, and an alias or pin can point at a core block, "contents": "@core/toc", the same way it points at a package. A version range on a core id (@core/toc@^1) is accepted and ignored: core blocks are versioned with the app. No package can claim the scope: manifest.json refuses an id under @core/, and plicine create-block --scope @core is refused too.

A field marked "markdown" in the tables below is tagged contentMediaType: "text/markdown" in its JSON Schema, which is how the edit form knows to give it a small page editor of its own rather than a one-line input: the content renders as it will look, an element opens for editing where it sits, and blocks can be added between elements.

Core blocks are part of the app, not .block files. Each has the same Interactive and Print a package block does, and a schema the CLI validates documents against. They render in the host page, so they can read what a sandboxed block can't, such as the document's headings for toc and the vault's documents for children. Text fields marked "markdown" below are rendered with the same markdown the document uses, including callouts, and never interpret raw HTML.

Width

Every block, core or package, can take more or less of the page than the text column, and so can a markdown table, image or paragraph. A block's width is written at the end of the fence's first line, as a Pandoc attribute, and isn't one of the block's props, so no schema lists it and the edit form never shows it:

```callout {width="wide"}
type: tip
text: Lorem ipsum dolor sit amet.
```
Value Infinite layout and exported site Paginated layout and PDF
narrow A fixed size: as much narrower than the column as wide is wider at its widest, 400px against the usual 680px column, and never less than half the column Comes in from each side as far as wide goes out
normal The text column. The same as writing no attribute The text column
wide Halfway between the text column and full, and no wider than --fx-width-wide (960px) Halfway into each side margin
full The whole width of the view the document scrolls in To within --fx-print-edge (6mm) of the sheet's edge

A wide or full block stays centred on the text column and is never narrower than it. normal is written as no attribute at all, and a value that isn't one of the three counts as normal. width='wide' and width=wide work too, and other attributes in the same braces ({#id .class width="full"}) are left alone. page-break ignores it, since it draws nothing.

The toolbar that shows over a block when you point at it, and the block's panel, both have a Narrow, Normal, Wide and Full control that writes the attribute on that one line. A code sample written the same way, ```mermaid {width="wide"}, is widened too. Only top-level elements have a width: a block inside a columns item or a card stays the width of what holds it.

Markdown with no fence line takes the same attribute in an HTML comment on the line directly above it:

<!-- {width="wide"} -->
| Item  | Qty |
| ----- | --- |
| Lorem | 1   |

The comment has to be on the very next line up, with no blank line between, and hold nothing but the braces. A comment like that with no element directly below it (a blank line under it, a fence, or the end of the document) applies to nothing, and Plicine shows it as its own text, faintly, so it can be found and deleted by clicking it; it's still left out of PDFs and exported sites. Plicine doesn't show a comment that applies, and neither do GitHub, Obsidian or a web page made with Pandoc, which all treat it as an ordinary comment. Open a table, image or paragraph for editing and the same control sits in the bar under the editor; it adds, changes or removes the comment, and emptying the element takes its comment out with it. A comment above a fence applies to nothing, since a fence has its own line for the attribute.

Everywhere else a fence still reads as a code block: markdown parsers take only the first word after the fence as the language, and Pandoc reads the braces as an attribute of the code block. A theme changes the widths with the tokens in The .theme package.

A wide callout

This callout is written with {width="wide"}, so it reaches past the text column on each side.

A full-width card

Lorem ipsum dolor sit amet, consectetur adipiscing elit. The card takes the whole width of the view on screen, and the page out to the printable edge on paper.

Lorem Ipsum Dolor Sit amet
Consectetur 100 200 A wide markdown table, set by the comment above it
Adipiscing 300 400 Sed do eiusmod tempor incididunt ut labore

page-break

Splits the document into print sections. On screen it renders as a labelled rule marking where the break falls; in print it produces nothing itself: the print layout reads its page prop and starts a new section (.fx-section) there.

Field Type Default Meaning
page string, optional none Named page for the section that follows, e.g. landscape. See "Named pages" in The .theme package. Non-letter/digit/_/- characters are stripped before use.
```page-break
page: landscape
```

toc

A table of contents built from the document's own headings, not a fixed list you write by hand. If the document has exactly one h1, that's treated as the title and excluded from the list; headings then start from h2. Otherwise every heading down to depth is included, starting from h1.

Field Type Default Meaning
title string "Contents" Heading shown above the list
depth integer, 1 to 6 3 Deepest heading level to include
```toc
title: On this page
depth: 2
```

The single live toc block at the top of this page uses the defaults (an empty body): title: Contents, depth: 3.

children

The pages below this one, as links, read from the vault each time the page renders, so the list follows documents as they're added, moved and renamed. On a folder's INDEX.md that's everything in the folder, which for the vault's own INDEX.md is the whole vault. On any other document it's the folder beside it with the same name, so plans.md lists what's in plans/; with no such folder the block says there's nothing below yet. Subfolders come first, as in the sidebar.

Every row is a link. A document opens itself, and a subfolder opens its INDEX.md, the same page a click on the folder in the sidebar opens (blank until something is written on it). A subfolder takes its title from that page when it exists and its name when it doesn't.

When depth lists more than one level, a subfolder with pages under it folds: the arrow in front of its name opens and closes it, and the name still opens the page. Subfolders start open unless collapsed is set. The paginated layout and PDFs show every level whatever collapsed says, since paper can't be clicked open. In a PDF the rows are text, not links, because the pages they name aren't in it (Links); in the paginated layout they still open.

Field Type Default Meaning
title string "Pages" Heading shown above the list; an empty string leaves it out
depth integer, 1 to 6 1 How many folder levels to list; 1 is only what's directly below
collapsed boolean false Start with every subfolder folded, so only the top level shows until an arrow is clicked
```children
title: In this section
depth: 2
collapsed: true
```

image

An image from the vault's assets/ folder (or an absolute http(s):/data: URL), with an optional caption.

Field Type Default Meaning
src string required Path inside the vault, e.g. assets/logo.png
alt string "" Text for screen readers
caption string, optional none Shown under the image as a <figcaption>
height string, optional none Any CSS height, e.g. 60mm
fit contain, cover, fill or none contain How the picture takes the block's width and the height, see below
align left, center or right center Horizontal alignment of the figure
```image
src: assets/logo.png
alt: Acme logo
height: 15mm
align: left
```

The block has no width of its own. How wide it can be is the block's width, the same {width="..."} every block takes: narrow, normal, wide or full.

fit takes the values of CSS object-fit. contain, the default, shows the whole picture: at its own size and never wider than the block, or at the height you give it and as wide as its shape makes that, which is how a logo or a screenshot is sized. The other three fill the block's whole width, however small the file is. With no height that's still the whole picture. A height then makes a frame of another shape: cover crops the picture to it, fill stretches it, and none keeps the picture's own size and lets the frame crop it. align is where the picture sits, so with a crop it's also the side that's kept. A banner across the page, margin to margin, cropped to 60mm:

```image {width="full"}
src: assets/workshop.jpg
alt: The workshop floor
height: 60mm
fit: cover
```

An image set to full width that's the first thing in the document is a header image: on screen, in the app's infinite layout and on an exported site, it runs up to the top of the view and out to both sides, with square corners, the way a cover does. Give it a height and fit: cover to make it a band. On paper, and in the paginated layout, it fills the head of the first page: up through the top margin and out to both sides, stopping at the sheet's printable edge (--fx-print-edge, 6mm), with square corners. Anything the theme runs in the top margin is left off that page.

A width: left over from before the field was removed is ignored, and the editor takes it out the next time the block is saved.

An image is edited on the image rather than in the panel: clicking it opens a small editor under it, see Editing an image. More options there opens the block's form, where src has a Choose… button beside the path and takes an image dropped on it or pasted into it. Either way the file is saved into assets/ and its path written into src; the path can still be typed by hand. A block with no src yet shows a note saying so in the app and prints as nothing. Pasting an image straight onto the page adds this block for you, see Pasting images.

callout

A note, tip, warning or quote in a coloured box with an icon, the same thing Obsidian and GitHub write as a quote starting with [!type]. The block form and the markdown form render identically; the block is there for the edit form and the picker, and it's written back into the > [!type] syntax before rendering, so the two can't drift. Documents covers the markdown form.

Field Type Default Meaning
type one of the types below note Sets the colour and icon
title string, optional the type's name Heading, as inline markdown
text markdown "" Body
fold open or closed, optional none Make the callout collapsible, starting open or closed

The types are Obsidian's: note, abstract, info, todo, tip, success, question, warning, failure, danger, bug, example and quote. In the markdown form the aliases Obsidian accepts work too (summary and tldr for abstract, hint and important for tip, check and done for success, help and faq for question, caution and attention for warning, fail and missing for failure, error for danger, cite for quote), and an unknown type is styled as a note.

```callout
type: warning
title: Check the dates
text: |
  The **holiday block** runs from 15 December to 1 January.

  - No site visits
  - Support by email only
```

Check the dates

The holiday block runs from 15 December to 1 January.

  • No site visits
  • Support by email only

A folded callout is a <details> element: fold: closed hides the body until the title is clicked, fold: open starts open. Print shows a closed one closed, as written.

Clicking a callout block edits it where it sits, as the > [!type] Title markdown it stands for. Saving reads the props back out of that text and writes only the ones that changed: the first line gives type, fold and title, an alias such as [!hint] is stored as its type, a type nobody knows leaves the one it had, and every other line is text without its >. Edit opens the form as before.

card

Markdown in a box. The card draws a border and a background and nothing else; whatever is inside is ordinary markdown, styled by the theme like the rest of the document. Its fence body is that markdown, not YAML, the way a ts-block's body is code.

Field Type Meaning
content markdown The whole body of the fence, taken verbatim
```card
### Ongoing support

A monthly clinic, a roadmap review and the on-call plan.
```

Ongoing support

A monthly clinic, a roadmap review and the on-call plan.

A card has no form. Click it and its markdown opens inside its box, the way an item of a columns block does, / menu included; what's saved is the fence's body, and the fence grows a backtick when a block added inside it would otherwise close it. Markdown on the toolbar opens the whole fence instead.

columns

Markdown items laid out in a grid of columns. Each item is its own element, added, removed and edited on its own, and nothing flows between columns. An item can hold blocks of its own, so a grid of cards is a columns block whose items are card fences.

Field Type Default Meaning
count integer, 1 to 6 2 How many columns
fill rows or columns rows rows: items go left to right, row by row. columns: items fill the first column top to bottom, then the next
stagger boolean false Let each item take only the height it needs, instead of filling its row
rowGap a named size, or an exact one the theme's How much room to leave between one item and the one below it
columnGap a named size, or an exact one the theme's How much room to leave between the columns
items list of markdown [] The items

By default the items are a grid: every item in a row is as tall as the tallest one there, so a row of cards has one baseline along the bottom however much text each holds, and fill decides whether items run across the rows or down the columns.

stagger: true deals the items into one stack per column instead. Every column starts at the top of the block and each item is only as tall as its content, so an item sits directly under the one above it and the columns end wherever their last item does. fill still sets the order: rows puts the first item top-left, the second top-right and so on, columns fills each column in turn.

Spacing is picked by eye. In the block's form, Row spacing and Column spacing are each a row of buttons like the width's: Auto, five pictures of two boxes with more and more room between them (None, Small, Medium, Large and Extra large, named beside the buttons once one is picked), and Custom. The page shows the change as you pick. Auto leaves it to the theme: 0.8em between rows and 1.2em between columns in the built-in theme, a little less when the items are cards. With stagger, row spacing is the room between the items stacked in a column.

Name Written Size
None none 0
Small small 0.5em
Medium medium 1em
Large large 2em
Extra large extra-large 3.5em

The sizes are in em, so they keep in step with the size of the text. If none of them is right, Custom opens a box for an exact size: a number with its unit, such as 12px, 1.5em or 6mm (any CSS length unit, or a bare 0). In the file the fields are rowGap and columnGap, named after the CSS properties they set, and hold either the name or the exact size:

rowGap: large
columnGap: 12mm

Clicking an item on the page edits that item where it is, the way clicking a paragraph does; see Editing a columns item. The form is where items are added, reordered and removed.

A run of short paragraphs with a bold lead-in, read down the first column then the next, the way terms of engagement or ground rules are usually set:

```columns
fill: columns
items:
  - "**Confidentiality first.** An NDA is signed before any work starts, on your paper or ours."
  - "**No lock-in.** The monthly arrangement can be cancelled with 30 days' notice."
  - "**Evidence before a decision.** The findings report lands before the build is agreed."
  - "**The practical side.** Contracted through Lorem Ipsum Ltd, which is VAT registered."
```

Confidentiality first. An NDA is signed before any work starts, on your paper or ours.

No lock-in. The monthly arrangement can be cancelled with 30 days' notice.

Evidence before a decision. The findings report lands before the build is agreed.

The practical side. Contracted through Lorem Ipsum Ltd, which is VAT registered.

A grid of cards, each item a card fence inside a YAML block scalar. The two cards hold different amounts of text and still line up along the bottom, because an item fills its row:

```columns
items:
  - |
    ```card
    ### Foundations

    Migrate files onto one platform and stand up the security baseline.
    ```
  - |
    ```card
    ### Build and coach

    The quoting library and the production board go live, with fortnightly coaching.
    ```
```

Foundations

Migrate files onto one platform and stand up the security baseline.

Build and coach

The quoting library and the production board go live, with fortnightly coaching.

Blocks inside markdown

Every markdown field of a core block (a columns item, a card, a callout's text) can hold fenced core blocks, rendered with the same components the top level uses: card, columns, callout and image, to any depth. toc, children, page-break and ts-block only make sense at the top level and stay code samples inside a block. Package blocks can't nest either: they run in sandboxed frames, and a frame can't be rendered inside a host component. This only applies inside blocks; a fence inside a document's own quote or list is still a code sample.

Styling the layout blocks

callout, card and columns carry no colours or fonts of their own beyond the app's neutral defaults. A theme restyles them through the class hooks listed in The .theme package: .fx-callout (with --fx-callout-color per type), .fx-card and .fx-columns/.fx-column-item. The built-in theme uses them to set the label face on callout titles and the paper-coloured card surface.

ts-block

Inline TypeScript/TSX, compiled and run the same way a .block package is, without needing to be packaged, installed or resolved by name. The fence's body isn't YAML like the other core blocks; it's the module's source, and it must export the same schema/Interactive/Print (or default) contract described in The .block package.

Field Type Meaning
code string The full body of the fence, taken verbatim as the module's source

Only react and zod can be imported; the plicine module a packaged block has does nothing here, since a ts-block has no props to write; there's no package.json, so no other dependency is possible. Each distinct block of code gets its own content hash (sha256("ts-block\0" + code)) and needs its own trust approval, same as a package: editing the code even slightly is a new hash and a new prompt.

```ts-block
import { z } from "zod";

export const schema = z.object({ name: z.string() });

export function Interactive({ name }: { name: string }) {
  return <p>Hello, {name}.</p>;
}

export function Print({ name }: { name: string }) {
  return <p>Hello, {name}.</p>;
}
```

ts-block code is edited in the document's source view, not through the block edit form. Only a fence named ts-block (or @core/ts-block) carries code; an alias that points at @core/ts-block is parsed as YAML like any other fence, so don't alias it.

Why ts-block, not ts

A fence named ```ts is the single most common way to show a TypeScript code sample in markdown, including in the documentation you're reading now. If that name executed as a block, every ordinary code sample in every vault would either need to be rewritten to avoid it or would silently start running as a block. Naming the executable one ts-block means a plain ```ts sample is always safe to write and never needs a warning badge.

Why language fences stay code

A fence's name is only ever treated as a block if it resolves to one: a core block, an installed block's alias or id, or a name a vault's aliases/frontmatter blocks pins to one. Anything else that also happens to be a recognised language name (the app knows the common ones, from python and sql to dockerfile and mermaid) renders as an ordinary syntax-highlighted code block with no warning. A name that isn't a recognised language and doesn't resolve to any block gets an "unknown block" warning instead, since that's more likely to be a typo than a deliberate code sample. This is also why installing a block whose alias happens to match a common language name (there's nothing stopping you registering alias: "python") would start intercepting every ```python fence in the vault; the resolver doesn't know the difference between "a language name nobody claimed" and "a language name a block claimed on purpose".