Markdown directives

Embed
Preview of the card
<a href="https://forumulate.com/guides/markdown-directives"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/0d9ee897-5c7e-496c-b934-fb3e3752a0ce/social-card.svg?v=3e7ae33eec8b" alt="Markdown directives" width="600" height="315"></a>

Every block a page can hold has a Markdown spelling, and the spelling round-trips: export a page with GET …/pages/{pageId}/markdown?form=body, send it back as body, and you get the same page. Plain Markdown covers prose, lists, code, tables and pictures (see Markdown in Forumulate); everything else — callouts, tabs, cards, steppers, columns, buttons and the rest — is a directive.

The same list is served as JSON, generated from the importer itself:

curl https://forumulate.com/api/docs/markdown-dialect

The four forms

Form

Written

Used for

container

:::name{attrs} on its own line, the body, then a bare :::

a block that holds other blocks

item

::name{attrs}, the body, then a bare ::

a child of a container: a tab, a step, a column

one line

:::name{attrs}:::

a block with no body: a button, an embed, a video

one-line item

::name{attrs} with no close

a self-closing child: a card

  • Closers count, not match. Containers nest freely — a callout inside a tab inside a tab group — and each bare ::: closes the innermost open container. :: only ever closes an item.

  • A one-line directive always has braces. :::feed{}::: is a block; :::feed::: is the text :::feed:::.

  • Attributes are key="value", key=value (no spaces) or a bare flag. Inside quotes, \" is a literal quote.

  • Up to three spaces of indent are allowed before a directive. Four or more is an indented code block, and a directive inside a ``` fence stays code.

  • An attribute a directive does not read is dropped and reported — the write answers X-Field-Warnings naming it — never kept silently and never a 422.

  • An unknown directive is transparent: its body is kept as ordinary blocks, and the same warning names it.

Attributes every directive takes

anchor, class, hook, stackOn, alternate, hideOn, shadow, radius, divider, gap, rowGap, spaceBefore, spaceAfter, spaceAround — the block's hooks and look keys. See Sections, video and block hooks in the authoring reference.

Directives

The attribute lists in this section are the importer's own table: anything else on these directives is dropped with a warning.

hint → hint

  • Written: :::hint{…} … :::

  • Attributes: style, kind, title, icon, tone, titleGap, edge

  • Same block as: :::attention, :::bug, :::caution, :::check, :::danger, :::deprecated, :::done, :::error, :::gloss, :::important, :::info, :::note, :::success, :::tip, :::warn, :::warning

:::hint{style="warning" title="Heads up"}
Mind the gap.
:::

expandable → expandable

  • Written: :::expandable{…} … :::

  • Attributes: title, label, style, icon, open

:::expandable{title="More detail"}
Hidden until opened.
:::

expandablegroup → expandableGroup

  • Written: :::expandablegroup{…} … :::

  • Holds: expandable

  • Attributes: exclusive, style

:::expandablegroup{exclusive}
:::expandable{title="One"}
First.
:::
:::expandable{title="Two"}
Second.
:::
:::

tabs → tabs

  • Written: :::tabs{…} … :::

  • Holds: tab

  • Attributes: syncKey, overflow, orientation, asCodeGroup, variant, tabStyle, activeColor, activeColorDark, activeUnderline, style

:::tabs
::tab{title="npm"}
npm install
::
::tab{title="pnpm"}
pnpm add
::
:::

tab → tab

  • Written: ::tab{…} … ::

  • Attributes: title, label

:::tabs
::tab{title="npm"}
npm install
::
:::

cards → cards

  • Written: :::cards{…} … :::

  • Holds: card

  • Attributes: columns, variant, border, iconAlign, iconSize, tileSize, titleWeight, titleSize, descriptionSize, background, backgroundDark, ctaPlacement, minWidth, gap, rowGap, divider, iconBand

:::cards{columns="2"}
::card{title="Quickstart" href="/quickstart"}
::card{title="API" href="/api"}
:::

card → card

  • Written: ::card{…} (one line, no close)

  • Also read: :::card{…}::: on one line

  • Attributes: title, href, cover, coverUrl, icon, description, iconUrl, iconStyle, color, cta, kicker, badge

:::cards
::card{title="Quickstart" href="/quickstart" icon="book"}
:::

stepper → stepper

  • Written: :::stepper{…} … :::

  • Holds: step

  • Attributes: headingLevel, titleSize, collapsible

:::stepper
::step{title="Install"}
Run the installer.
::
::step{title="Sign in"}
Use your account.
::
:::

step → step

  • Written: ::step{…} … ::

  • Attributes: title, label, icon, badges, aside

:::stepper
::step{title="Install"}
Run the installer.
::
:::

columns → columns

  • Written: :::columns{…} … :::

  • Holds: column

  • Attributes: breakout, caption

:::columns
::column
Left.
::
::column
Right.
::
:::

column → column

  • Written: ::column{…} … ::

  • Attributes: width, role, sticky

:::columns
::column{width="60"}
Left.
::
::column
Right.
::
:::

grid → grid

  • Written: :::grid{…} … :::

  • Holds: gridItem

  • Attributes: cols

:::grid{cols="2"}
::gridItem
One.
::
::gridItem
Two.
::
:::

gridItem → gridItem

  • Written: ::gridItem{…} … ::

  • Attributes: colSpan, colStart, rowStart, rowSpan, mdSpan, smSpan, bg, pad, align, border, gradient, textAlign, icon, iconStyle, accent

:::grid{cols="2"}
::gridItem{colSpan="2"}
Wide.
::
:::

buttons → buttons

  • Written: :::buttons{…} … :::

  • Holds: button

  • Attributes: align

:::buttons{align="center"}
:::button{label="Start" href="/start"}:::
:::button{label="Docs" href="/docs" style="secondary"}:::
:::

button → button

  • Written: :::button{…}::: (one line)

  • Also read: :::button{…}, the label on the next line, then :::

  • Attributes: label, href, style, icon, action, query, targetSlug, width

:::button{label="Deploy" href="/deploy" style="primary"}:::

updates → updates

  • Written: :::updates{…} … :::

  • Holds: update

  • Attributes: format

:::updates
::update{date="2026-10-01" tags="api"}
Shipped the thing.
::
:::

update → update

  • Written: ::update{…} … ::

  • Attributes: date, tags

:::updates
::update{date="2026-10-01"}
Shipped.
::
:::

audience → audience

  • Written: ::audience{…} … ::

  • Also read: :::audience{…} … :::

  • Attributes: claim, equals, condition

::audience{claim="plan" equals="pro"}
Only Pro readers see this.
::

embed → embed

  • Written: :::embed{…}::: (one line)

  • Also read: ::embed{url="…"} on one line

  • Attributes: url

:::embed{url="https://www.youtube.com/watch?v=dQw4w9WgXcQ"}:::

reusable → reusableRef

  • Written: :::reusable{…}::: (one line)

  • Attributes: id, name

:::reusable{id="0b6c…" name="support-footer"}:::

customcode → customCode

  • Written: :::customcode{…} … :::

  • Also read: :::customcode{html="…" css="…"}::: on one line

  • Attributes: data, html, css

:::customcode{css=".hero { color: tomato }"}
<div class="hero">Hello</div>
:::

prompt → prompt

  • Written: :::prompt{…}::: (one line)

  • Also read: :::prompt{…}, the prompt verbatim, then :::; a ```prompt fence

  • Attributes: text, label, variant, wrap, targets, showText, actions, align, textStyle, collapse, layout, primary, icon, header, copyStyle, phoneTargets, labelCase, background, backgroundDark, collapseStyle, openInDesktop

:::prompt{label="Ask" text="Summarise this page"}:::

field → field

  • Written: :::field{…} … :::

  • Attributes: name, type, required, default, deprecated, location, badges

:::field{name="limit" type="integer" location="query" default="10"}
How many rows to return.
:::

example → example

  • Written: :::example{…} … :::

  • Attributes: kind, title

:::example{kind="request" title="cURL"}
```sh
curl https://api.example.com/v1/items
```
:::

filetree → fileTree

  • Written: :::filetree{…} … :::

  • Attributes: —

:::filetree
- src/
  - index.ts
- package.json
:::

swatch → swatch

  • Written: :::swatch{…}::: (one line)

  • Attributes: colors, layout, data

:::swatch{colors="Brand #4f46e5, Ink #111111"}:::

Directives with their own vocabulary

These blocks are read by dedicated readers whose attributes are documented with the block in the authoring reference. data blocks carry their whole configuration as one encoded attribute: build the block once (in the editor or as JSON), export the page, and copy the line.

codeGroup → codeGroup

  • Written: :::codeGroup{…} … :::

  • Attributes: see codeGroup in the authoring reference

  • Same block as: :::codegroup

:::codeGroup{syncKey="pm"}
```sh title="npm"
npm install x
```
```sh title="pnpm"
pnpm add x
```
:::

video → video

  • Written: :::video{…}::: (one line)

  • Attributes: see video in the authoring reference

:::video{src="https://cdn.example.com/demo.mp4"}:::

spacer → spacer

  • Written: :::spacer{…}::: (one line)

  • Attributes: see spacer in the authoring reference

:::spacer{size="lg"}:::

rateLimit → rateLimit

  • Written: :::rateLimit{…}::: (one line)

  • Attributes: see rateLimit in the authoring reference

:::rateLimit{limit="100" window="minute" scope="per API key"}:::

errorCodes → errorCodes

  • Written: :::errorCodes{…}::: (one line)

  • Attributes: see errorCodes in the authoring reference

:::errorCodes{rows="rate_limited|429|Too many requests."}:::

systemStatus → systemStatus

  • Written: :::systemStatus{…}::: (one line)

  • Attributes: see systemStatus in the authoring reference

:::systemStatus{org="00000000-0000-4000-8000-000000000001" space="00000000-0000-4000-8000-000000000002" style="status"}:::

form → form

  • Written: :::form{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

quiz → quiz

  • Written: :::quiz{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

event → event

  • Written: :::event{…}::: (one line)

  • Attributes: see event in the authoring reference

:::event{spaceId="00000000-0000-4000-8000-000000000000" event="launch-party"}:::

rsvp → eventRsvp

  • Written: :::rsvp{…}::: (one line)

  • Attributes: see eventRsvp in the authoring reference

:::rsvp{spaceId="00000000-0000-4000-8000-000000000000" event="launch-party" label="Save my seat"}:::

contactform → contactForm

  • Written: :::contactform{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

product → product

  • Written: :::product{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

lottie → lottie

  • Written: :::lottie{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

productCard → productCard

  • Written: :::productCard{…}::: (one line)

  • Attributes: see productCard in the authoring reference

:::productCard{url="https://shop.example.com/p/air-max-1" endpoint="https://api.example.com/cards/air-max-1" layout="horizontal"}:::

sources → sources

  • Written: :::sources{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

corrections → corrections

  • Written: :::corrections{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

compare → imageCompare

  • Written: :::compare{…}::: (one line)

  • Attributes: see imageCompare in the authoring reference

:::compare{before="https://cdn.example.com/a.png" after="https://cdn.example.com/b.png"}:::

pricing → pricing

  • Written: :::pricing{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

testimonials → testimonials

  • Written: :::testimonials{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

logos → logoWall

  • Written: :::logos{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

stats → stats

  • Written: :::stats{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

hero → hero

  • Written: :::hero{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

person → person

  • Written: :::person{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

offices → officeMap

  • Written: :::offices{…}::: (one line)

  • Attributes: data (the block as encoded JSON — export one to get it)

  • Written: :::carousel{…} … :::

  • Holds: slide

  • Attributes: see carousel in the authoring reference

:::carousel{loop}
::slide
First.
::
::slide
Second.
::
:::

slide → carouselSlide

  • Written: ::slide{…} … ::

  • Attributes: see carouselSlide in the authoring reference

:::carousel
::slide{label="One"}
First.
::
:::
  • Written: :::gallery{…} … :::

  • Attributes: see gallery in the authoring reference

:::gallery{columns="2"}
![One](https://cdn.example.com/1.png)

![Two](https://cdn.example.com/2.png)
:::

marquee → marquee

  • Written: :::marquee{…} … :::

  • Attributes: see marquee in the authoring reference

:::marquee{speed="slow"}
Acme · Globex · Initech
:::
  • Written: :::modal{…} … :::

  • Attributes: see modal in the authoring reference

:::modal{label="Watch the demo" title="Demo"}
Anything at all.
:::

layer → layer

  • Written: :::layer{…}::: (one line)

  • Attributes: see layer in the authoring reference

:::layer{kind="image" src="https://cdn.example.com/badge.png"}:::

story → story

  • Written: :::story{…} … :::

  • Holds: scene

  • Attributes: see story in the authoring reference

:::story
::scene{media="https://cdn.example.com/1.png"}
Step one.
::
:::

scene → storyStep

  • Written: ::scene{…} … ::

  • Attributes: see storyStep in the authoring reference

:::story
::scene
Step one.
::
:::

playground → playground

  • Written: :::playground{…} … :::

  • Attributes: see playground in the authoring reference

:::playground
```html
<button>Hi</button>
```
:::

walkthrough → walkthrough

  • Written: :::walkthrough{…} … :::

  • Holds: step

  • Attributes: see walkthrough in the authoring reference

:::walkthrough{language="ts"}
```ts
const a = 1;
const b = 2;
```
::step{lines="1"}
The first line.
::
:::

step → walkthroughStep

  • Written: ::step{…} … ::

  • Attributes: see walkthroughStep in the authoring reference

:::walkthrough
```ts
const a = 1;
```
::step{lines="1"}
One line.
::
:::

feed → spaceFeed

  • Written: :::feed{…}::: (one line)

  • Attributes: see spaceFeed in the authoring reference

:::feed{}:::

categories → spaceCategories

  • Written: :::categories{…}::: (one line)

  • Attributes: see spaceCategories in the authoring reference

:::categories{}:::

band → spaceBand

  • Written: :::band{…}::: (one line)

  • Attributes: see spaceBand in the authoring reference

:::band{name="search"}:::

leaderboard → spaceLeaderboard

  • Written: :::leaderboard{…}::: (one line)

  • Attributes: see spaceLeaderboard in the authoring reference

:::leaderboard{}:::

Blocks written in plain Markdown

Block

Spelling

paragraph

a paragraph

heading

# … ######

blockquote

>

bulletList

-

orderedList

1.

listItem

an item of either list

checklist

- [ ] / - [x]

checklistItem

one - [ ] line

codeBlock

a ``` fence (the info string carries the options)

mermaid

a ```mermaid fence

math

$$ … $$

horizontalRule

---

image

![alt](url)

file

[name](url) to an uploaded file

table

a `

tableRow

a table row

tableHeader

a header cell

tableCell

a cell

Blocks with no hand-written form

Block

Why

section

a shape's named slot — write slots with sections on the page write, not as a directive

drawing

drawn in the editor; exported as a <!-- forumulate:drawing --> marker

extensionBlock

an installed extension's block; exported as a marker comment

openapi

generated by the OpenAPI importer (POST …/docs/openapi/import)

This page is generated from the importer's own tables (packages/docs-schema/src/markdown-dialect.ts); a test fails when the two disagree.

The Forumulate teamUpdated