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.

Navigation

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: Marp's html directive is enabled (so this should "just work"), but if you've passed html: false in your frontmatter, Marp will escape <br>, <span>, etc.

Fix: remove the html: false directive or set html: true.

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