Slide decks
Anchorify renders two slide engines server-side:
| Engine | Stored as | What it is |
|---|---|---|
| Marp | slides_marp |
Marp Markdown. Static slide deck rendered to HTML+CSS at publish time. Best for handouts and async sharing. |
| reveal.js | slides_reveal |
reveal.js. Interactive deck with transitions, keyboard navigation, fragments. Best for live presentations in the browser. |
Both render full-screen on the share URL — no 760px reading column, no comment rail. Recipients open the link and see a deck.
Activation#
Anchorify picks the slide engine in four ways, in order of precedence:
--type slides_marp/--type slides_revealonanchorify publish(or"type": "slides_marp"in the API).- File extension —
.marpfiles publish asslides_marp,.revealjsfiles asslides_reveal. - YAML frontmatter at the very top of a
.mdfile (most common):
or--- marp: true ------ reveal: true --- - Web "Render as" dropdown on the upload form.
Once detected, the file renders as a slide deck on the share URL. The frontmatter block is not shown to recipients.
Frontmatter rules#
Frontmatter detection is strict-by-default but tolerant of a few common authoring quirks:
- Allowed before the
---opener: UTF-8 BOM, blank lines, one HTML comment block (e.g.<!-- build instructions -->). Anything else — markdown text, a heading, a second comment — disables detection. - Key spelling:
marp:andreveal:(case-sensitive on the key). - Truthy values:
true,True,TRUE,yesall activate; everything else (1,on, quoted strings) does not. - Block must close with a matching
---on its own line. An unclosed block falls back to plain markdown — the dashes render as<hr>and the content below as headings.
Example, minimal Marp deck:
---
marp: true
theme: default
---
# First slide
---
# Second slide
Example, minimal reveal.js deck:
---
reveal: true
---
# First slide
---
# Second slide
Slide separators#
After the frontmatter, separate slides with --- on its own line, surrounded by blank lines. Without separators, your entire deck renders as one slide.
reveal.js also supports -- (two dashes) for vertical "sub-slides" — press ↓ to drop down, → to advance horizontally.
Navigation#
Marp decks expose three ways to move between slides on the recipient side:
- Keyboard —
→/↓/PageDown/Spacefor next;←/↑/PageUpfor previous.Home/Endjump to the first/last slide. - Tap / click — tap the left ~25 % of the viewport to go back; tap anywhere else to advance. Links, buttons, the share header, and the on-screen nav pill are excluded so they still work normally.
- Swipe (touch devices) — left-swipe → next, right-swipe → previous.
A small on-screen pill at the bottom of the viewport shows current / total and exposes ◀ / ▶ chevrons on mobile-sized screens (and on touch input). It fades after a moment of inactivity and reappears on any interaction. The hash fragment (#3 for slide 3) stays in sync so recipients can deep-link to a specific slide.
reveal.js decks use reveal's own controls — see revealjs.com for the full key map.
Common pitfalls#
Frontmatter not detected#
Symptom: the share renders as a plain markdown page with --- followed by marp: true as visible text.
Cause: something other than a single HTML comment sits before the frontmatter — typically a build comment that was wrapped in a ### How to build heading, or two HTML comments stacked, or some text added during editing.
Fix: move the YAML frontmatter to the absolute top of the file, or use --type slides_marp to force detection. You can also rename the file to deck.marp to bypass the frontmatter check.
Whole deck rendering as one slide#
Symptom: scrolling instead of paginating; one giant slide.
Cause: missing --- slide separators between sections.
Fix: put a --- line (with blank lines above and below) between each slide.
Marp directives ignored (theme, header, paginate)#
Symptom: deck renders with engine defaults instead of the directives you set (theme: gaia, header: 'My Title', paginate: true).
Cause: the frontmatter parser only honors directives in a block at the very top of the file. The render survives because the V3.3 leniency strips the same preamble the sniff does, but if your frontmatter is malformed (no closing ---, embedded inside content) the directives never reach Marp.
Fix: validate the frontmatter shape — --- opener on line 1 (or right after a leading comment), keys on their own lines, --- closer.
Inline HTML being escaped instead of rendered#
Symptom: raw HTML tags showing in the slide instead of formatting.
Cause: Inline HTML for layout — <div class="columns">, <span style="…">, <br>, tables, images, lists — is rendered. But for security, Anchorify renders decks against a fixed allowlist of safe tags and attributes: <script>, <iframe>, <object>, <embed>, any on*= event handler, and javascript: URLs are escaped to inert text rather than executed (decks are served on the same origin as your dashboard, so executable deck HTML would be a cross-account risk). If a tag you expected to render is showing as text, it's outside that allowlist.
Fix: stick to layout/structural HTML for slide formatting. If you genuinely need scripted/interactive HTML, publish it as the html_embed content type instead — that runs in a sandboxed iframe isolated from your account session.
Tables and overflow#
Marp slides are a fixed 1280×720 box with overflow: hidden — anything past the bottom of the slide silently disappears. Tables that an agent generates from real data routinely overflow this; the user only finds out when they preview the deck.
Two safeguards:
1. Scrollable table containers (render-side). Every <table> inside a slide is wrapped in a scroll container with a sticky header. Long tables stay visible — the recipient scrolls inside the slide. This is a defensive fallback; not a substitute for splitting content sensibly.
2. Lint warnings (publish-time). The analyzer counts table rows, code-fence lines, and content lines per slide, and warns when any single slide is likely to overflow:
| Kind | Threshold | Suggested fix |
|---|---|---|
slide_table_too_long |
>14 table rows on one slide | Split the table across slides, or render this as a CSV share. |
slide_code_too_long |
>26 lines in fenced code blocks | Trim non-essential lines or split the block. |
slide_content_too_long |
>32 content lines (excluding tables) | Split the slide. |
Agents should POST /api/v1/lint before publishing and split overflowing slides on the warnings. The thresholds correspond to the default Marp theme on 1280×720; custom themes with different font sizes may differ.
Lint before publishing#
Run anchorify lint deck.md to check the file against the analyzer before publishing. The lint API surfaces the same warnings the owner-banner shows on the share-render page, so you'll see "slide_frontmatter_unrecognized" or "slide_deck_no_separators" before you push to your client.
Agents calling the API should POST to /api/v1/lint with {content, filename, content_type?} and inspect the warnings array. See Lint API.
What's not supported#
- Slidev — separate engine, would add ~5MB of deps for a third format. Decks portable to Slidev are not portable here.
- PPTX export — Marp can export to PPTX via the local marp-cli; the server-side renderer here only produces HTML.
- Speaker notes view — reveal.js has one (press
Son the deck) but the URL doesn't deep-link to it.
Next steps#
- Content types and rendering — the rest of the renderers.
- Lint API — pre-publish format checks.
- Command-line —
--typeand thelintsubcommand.