---
main_commit: 98be8a667
analyzed_date: 2026-02-23
key_files:
  - src/format/reveal/format-reveal.ts
  - src/format/reveal/constants.ts
  - src/resources/formats/revealjs/pandoc/template.html
  - src/resources/formats/revealjs/pandoc/revealjs.template
---

# Revealjs Format Architecture

How Quarto configures reveal.js presentations, covering metadata handling, template rendering, and the division of responsibilities between TypeScript and Pandoc templates.

## Key Files

| File | Role |
|------|------|
| `src/format/reveal/format-reveal.ts` | Format definition, metadata normalization, extras |
| `src/format/reveal/constants.ts` | Metadata key constants |
| `src/resources/formats/revealjs/pandoc/template.html` | Quarto's active Pandoc template |
| `src/resources/formats/revealjs/pandoc/revealjs.template` | Reference copy of Pandoc's upstream template |

## Two Template Files

Quarto maintains two revealjs template files. They serve different purposes:

**`revealjs.template`** is a direct copy of Pandoc's `default.revealjs`, auto-generated by `package/src/common/update-pandoc.ts` during Pandoc version updates. Do not modify this file — changes will be overwritten on the next Pandoc update.

**`template.html`** is Quarto's active template, specified in `formatExtras()` via `templateContext`. It diverges from the upstream template where Quarto needs different behavior (type-safe rendering, additional features, etc.).

## Metadata Handling Pattern

Revealjs configuration flows through three stages with distinct responsibilities:

### Stage 1: Normalization — `revealResolveFormat()`

Maps user-facing YAML keys to reveal.js configuration keys. Runs early in format resolution.

Responsibilities:
- Map compound YAML structures to flat metadata (e.g., `scroll-view.snap` → `scrollSnap`)
- Normalize values (e.g., `navigationMode: "vertical"` → `"default"`)
- Create helper flags for template type handling (e.g., `scrollProgressAuto`)
- Remove intermediate metadata keys (e.g., delete `scroll-view` after extracting sub-options)

Does NOT set defaults — only transforms what the user provided.

### Stage 2: Defaults — `extras.metadata` in `formatExtras()`

Sets opinionated default values that can be overridden by user metadata. These are the lowest priority in the metadata chain.

```typescript
// General revealjs defaults
extras.metadata = {
  ...extras.metadata,
  ...revealMetadataFilter({
    width: 1050,
    height: 700,
    center: false,
    transition: "none",
    // ...
  }),
};

// Conditional defaults (e.g., scroll-view options when view is "scroll")
if (format.metadata[kView] === "scroll") {
  extras.metadata = {
    ...extras.metadata,
    [kScrollSnap]: "mandatory",
    [kScrollLayout]: "full",
    [kScrollActivationWidth]: 0,
  };
}
```

Setting defaults explicitly (rather than relying on Pandoc's `defField`) ensures the template always has values to render.

### Stage 3: Post-processing — `fixupRevealJsInitialization()`

DOM manipulation of the rendered HTML, handling values that can't be fixed in the template:

- Quoting `slideNumber` string values (e.g., `h.v` → `'h.v'`)
- Quoting percentage-based `width`/`height` values
- Injecting `extraConfig` values (options not in the template)
- Registering plugins

Use this stage only when template-level handling isn't possible.

### Metadata Priority (highest to lowest)

1. `metadataOverride` — forces values regardless of user settings
2. `format.metadata` — user values + normalization from `revealResolveFormat()`
3. Pandoc `defField` — Pandoc writer defaults for unset variables
4. `extras.metadata` — Quarto's opinionated defaults

## Template Type Handling

Pandoc templates render values as text. This creates type mismatches when reveal.js expects specific JavaScript types. Quarto's `template.html` uses conditional guards to render correct JS types.

### The Problem

Pandoc template variables have limited type awareness:
- `$var$` renders `BoolVal True` as `true`, `BoolVal False` as `false` (correct for JS booleans)
- `'$var$'` always renders as a quoted string, even for `false` → `'false'` (wrong — truthy in JS)
- Numbers rendered inside quotes become strings: `'$var$'` with `0` → `'0'` (wrong if JS expects a number)

### Pattern: Mixed-Type Options

When an option accepts both strings and booleans (e.g., `scrollSnap: "mandatory" | "proximity" | false`):

```
$if(scrollSnap)$
        scrollSnap: '$scrollSnap/nowrap$',
$else$
        scrollSnap: false,
$endif$
```

This works because Pandoc's `$if()$` evaluates `BoolVal False` as false, so:
- String values (`"mandatory"`, `"proximity"`) → `$if$` is true → quoted output
- Boolean `false` → `$if$` is false → `$else$` renders unquoted `false`

### Pattern: String "auto" with Boolean Fallback

When an option accepts `"auto" | true | false` (e.g., `scrollProgress`), a helper flag avoids rendering `"auto"` as a boolean:

TypeScript (normalization stage):
```typescript
if (value === "auto" || value === undefined) {
  format.metadata[kHelperFlag] = true;
  delete format.metadata[kOriginalKey];
}
```

Template:
```
$if(helperFlag)$
        option: 'auto',
$elseif(option)$
        option: $option$,
$else$
        option: false,
$endif$
```

### Pattern: Numeric Options

When reveal.js checks `typeof value === 'number'`, the template must NOT quote the value:

```
        scrollActivationWidth: $scrollActivationWidth$,
```

Not `'$scrollActivationWidth$'` — that renders as string `'0'` instead of number `0`.

## Known Pandoc Template Issues

Pandoc's upstream `revealjs.template` (copied to `revealjs.template`) has type issues in the scroll-view block that Quarto's `template.html` fixes. Tracked in [jgm/pandoc#11486](https://github.com/jgm/pandoc/issues/11486):

- `scrollSnap: '$scrollSnap$'` renders `false` as string `'false'` (truthy in JS)
- `scrollActivationWidth: '$scrollActivationWidth$'` renders numbers as strings
- `scrollProgress` defField defaults to `true` instead of reveal.js's `'auto'`

## Adding New Reveal.js Options

When adding support for a new reveal.js configuration option:

1. Add the constant to `constants.ts`
2. If the option needs YAML normalization (e.g., a compound structure), add to `revealResolveFormat()`
3. If the option needs a default value, add to `extras.metadata` in `formatExtras()`
4. If the option needs type-safe rendering, add to `template.html` with appropriate `$if/$else$` guards
5. If the option can only be handled post-render, add to `extraConfig` (last resort)
6. Add smoke-all tests covering type edge cases
