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:

  1. --type slides_marp / --type slides_reveal on anchorify publish (or "type": "slides_marp" in the API).
  2. File extension.marp files publish as slides_marp, .revealjs files as slides_reveal.
  3. YAML frontmatter at the very top of a .md file (most common):
    ---
    marp: true
    ---
    
    or
    ---
    reveal: true
    ---
    
  4. 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: and reveal: (case-sensitive on the key).
  • Truthy values: true, True, TRUE, yes all 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.

Marp decks expose three ways to move between slides on the recipient side:

  • Keyboard / / PageDown / Space for next; / / PageUp for previous. Home / End jump 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 S on the deck) but the URL doesn't deep-link to it.

Next steps#