---
name: guide-slides
description: Publish slide decks as shareable URLs. Marp + reveal.js, frontmatter shape, and how to avoid common pitfalls.
---

# Slide decks

Anchorify renders two slide engines server-side:

| Engine     | Stored as       | What it is |
| ---------- | --------------- | ---------- |
| **Marp**     | `slides_marp`   | [Marp Markdown](https://marp.app/). Static slide deck rendered to HTML+CSS at publish time. Best for handouts and async sharing. |
| **reveal.js** | `slides_reveal` | [reveal.js](https://revealjs.com/). 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):
   ```yaml
   ---
   marp: true
   ---
   ```
   or
   ```yaml
   ---
   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:

```markdown
---
marp: true
theme: default
---

# First slide

---

# Second slide
```

Example, minimal reveal.js deck:

```markdown
---
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](https://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](/docs/guide-lint).

## 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

- [Content types and rendering](/docs/content-types) — the rest of the renderers.
- [Lint API](/docs/guide-lint) — pre-publish format checks.
- [Command-line](/docs/cli) — `--type` and the `lint` subcommand.
